From 6700ff35bec4b3ced73f0de8c1aad1f740facbc2 Mon Sep 17 00:00:00 2001 From: Hoan Luu Huu <110280845+xquanluu@users.noreply.github.com> Date: Wed, 28 May 2025 18:28:48 +0700 Subject: [PATCH] support fetching application with pagination (#450) * support fetching application with pagination * pagination for voip carrier * wip * wip * wip * support phone number pagination * wip * wip * wip --- lib/models/application.js | 55 ++++++++++++++++++++++------- lib/models/phone-number.js | 51 ++++++++++++++++++-------- lib/models/voip-carrier.js | 46 ++++++++++++++++++++++++ lib/routes/api/accounts.js | 33 +++++++++++++++-- lib/routes/api/applications.js | 20 +++++++++-- lib/routes/api/phone-numbers.js | 40 +++++++++++---------- lib/routes/api/service-providers.js | 27 +++++++++++--- lib/routes/api/voip-carriers.js | 38 +++++++++++++------- 8 files changed, 241 insertions(+), 69 deletions(-) diff --git a/lib/models/application.js b/lib/models/application.js index efbf0e1..b83074f 100644 --- a/lib/models/application.js +++ b/lib/models/application.js @@ -36,24 +36,55 @@ class Application extends Model { super(); } + static _criteriaBuilder(obj, args) { + let sql = ''; + if (obj.account_sid) { + sql += ' AND app.account_sid = ?'; + args.push(obj.account_sid); + } + if (obj.service_provider_sid) { + sql += ' AND app.account_sid in (SELECT account_sid from accounts WHERE service_provider_sid = ?)'; + args.push(obj.service_provider_sid); + } + if (obj.name) { + sql += ' AND app.name LIKE ?'; + args.push(`%${obj.name}%`); + } + return sql; + } + + static countAll(obj) { + let sql = 'SELECT COUNT(*) AS count FROM applications app WHERE 1 = 1'; + const args = []; + sql += Application._criteriaBuilder(obj, args); + return new Promise((resolve, reject) => { + getMysqlConnection((err, conn) => { + if (err) return reject(err); + conn.query({sql}, args, (err, results) => { + conn.release(); + if (err) return reject(err); + resolve(results[0].count); + }); + }); + }); + } + /** * list all applications - for all service providers, for one service provider, or for one account, * or by an optional name */ - static retrieveAll(service_provider_sid, account_sid, name) { + static retrieveAll(obj) { + const { page, page_size = 50 } = obj || {}; let sql = retrieveSql + ' WHERE 1 = 1'; const args = []; - if (account_sid) { - sql = `${sql} AND app.account_sid = ?`; - args.push(account_sid); - } - else if (service_provider_sid) { - sql = `${sql} AND account_sid in (SELECT account_sid from accounts WHERE service_provider_sid = ?)`; - args.push(service_provider_sid); - } - if (name) { - sql = `${sql} AND app.name = ?`; - args.push(name); + sql += Application._criteriaBuilder(obj, args); + + if (page !== null && page !== undefined) { + const limit = Number(page_size); + const offset = Number(page > 0 ? (page - 1) : page) * limit; + sql += ' LIMIT ? OFFSET ?'; + args.push(limit); + args.push(offset); } return new Promise((resolve, reject) => { getMysqlConnection((err, conn) => { diff --git a/lib/models/phone-number.js b/lib/models/phone-number.js index df8a36e..e875d4c 100644 --- a/lib/models/phone-number.js +++ b/lib/models/phone-number.js @@ -26,24 +26,45 @@ class PhoneNumber extends Model { return rows; } - static async retrieveAllByCriteria({ - service_provider_sid, account_sid, filter - }) { + static _criteriaBuilder(obj, params) { + let sql = ''; + if (obj.service_provider_sid) { + sql += ' AND account_sid IN (SELECT account_sid FROM accounts WHERE service_provider_sid = ?)'; + params.push(obj.service_provider_sid); + } + if (obj.account_sid) { + sql += ' AND account_sid = ?'; + params.push(obj.account_sid); + } + if (obj.filter) { + sql += ' AND number LIKE ?'; + params.push(`%${obj.filter}%`); + } + + return sql; + } + + static async countAll(obj) { + let sql = 'SELECT COUNT(*) AS count FROM phone_numbers WHERE 1 = 1'; + const args = []; + sql += PhoneNumber._criteriaBuilder(obj, args); + const [rows] = await promisePool.query(sql, args); + return rows[0].count; + } + + static async retrieveAllByCriteria(obj) { let sql = 'SELECT * FROM phone_numbers WHERE 1=1'; const params = []; - if (service_provider_sid) { - sql += ' AND account_sid IN (SELECT account_sid FROM accounts WHERE service_provider_sid = ?)'; - params.push(service_provider_sid); - } - if (account_sid) { - sql += ' AND account_sid = ?'; - params.push(account_sid); - } - if (filter) { - sql += ' AND number LIKE ?'; - params.push(`%${filter}%`); - } + const { page, page_size = 50 } = obj || {}; + sql += PhoneNumber._criteriaBuilder(obj, params); sql += ' ORDER BY number'; + if (page !== null && page !== undefined) { + const limit = Number(page_size); + const offset = Number(page > 0 ? (page - 1) : page) * limit; + sql += ' LIMIT ? OFFSET ?'; + params.push(limit); + params.push(offset); + } const [rows] = await promisePool.query(sql, params); return rows; } diff --git a/lib/models/voip-carrier.js b/lib/models/voip-carrier.js index 9e01a89..6fee2c6 100644 --- a/lib/models/voip-carrier.js +++ b/lib/models/voip-carrier.js @@ -8,6 +8,52 @@ class VoipCarrier extends Model { constructor() { super(); } + + static _criteriaBuilder(obj, args) { + let sql = ''; + if (obj.account_sid) { + sql += ' AND vc.account_sid = ?'; + args.push(obj.account_sid); + } else { + sql += ' AND vc.account_sid IS NULL'; + } + if (obj.service_provider_sid) { + sql += ' AND vc.service_provider_sid = ?'; + args.push(obj.service_provider_sid); + } + if (obj.name) { + sql += ' AND vc.name LIKE ?'; + args.push(`%${obj.name}%`); + } + + return sql; + } + + static async countAll(obj) { + let sql = 'SELECT COUNT(*) AS count FROM voip_carriers vc WHERE 1 = 1'; + const args = []; + sql += VoipCarrier._criteriaBuilder(obj, args); + const [rows] = await promisePool.query(sql, args); + return rows[0].count; + } + + static async retrieveByCriteria(obj) { + let sql = 'SELECT * from voip_carriers vc WHERE 1 =1'; + const args = []; + sql += VoipCarrier._criteriaBuilder(obj, args); + if (obj.page !== null && obj.page !== undefined) { + const limit = Number(obj.page_size || 50); + const offset = (Number(obj.page) - 1) * limit; + sql += ' LIMIT ? OFFSET ?'; + args.push(limit, offset); + } + const [rows] = await promisePool.query(sql, args); + if (rows) { + rows.map((r) => r.register_status = JSON.parse(r.register_status || '{}')); + } + return rows; + } + static async retrieveAll(account_sid) { if (!account_sid) return super.retrieveAll(); const [rows] = await promisePool.query(retrieveSql, account_sid); diff --git a/lib/routes/api/accounts.js b/lib/routes/api/accounts.js index a795c7f..c9a4682 100644 --- a/lib/routes/api/accounts.js +++ b/lib/routes/api/accounts.js @@ -23,7 +23,8 @@ const { } = require('./utils'); const short = require('short-uuid'); const VoipCarrier = require('../../models/voip-carrier'); -const { encrypt, obscureBucketCredentialsSensitiveData, isObscureKey } = require('../../utils/encrypt-decrypt'); +const { encrypt, obscureBucketCredentialsSensitiveData, + isObscureKey, decrypt } = require('../../utils/encrypt-decrypt'); const { testS3Storage, testGoogleStorage, testAzureStorage } = require('../../utils/storage-utils'); const translator = short(); @@ -92,8 +93,34 @@ router.get('/:sid/Applications', async(req, res) => { try { const account_sid = parseAccountSid(req); await validateRequest(req, account_sid); - const results = await Application.retrieveAll(null, account_sid); - res.status(200).json(results); + const {page, page_size, name} = req.query || {}; + const isPaginationRequest = page !== null && page !== undefined; + let results = []; + let total = 0; + if (isPaginationRequest) { + total = await Application.countAll({account_sid, name}); + results = await Application.retrieveAll({ + account_sid, name, page, page_size + }); + } else { + results = await Application.retrieveAll({account_sid}); + } + const ret = results.map((a) => { + if (a.env_vars) { + a.env_vars = JSON.parse(decrypt(a.env_vars)); + return a; + } else { + return a; + } + }); + + const body = isPaginationRequest ? { + total, + page: Number(page), + page_size: Number(page_size), + data: ret + } : ret; + res.status(200).json(body); } catch (err) { sysError(logger, res, err); } diff --git a/lib/routes/api/applications.js b/lib/routes/api/applications.js index 5b6f33e..6020bec 100644 --- a/lib/routes/api/applications.js +++ b/lib/routes/api/applications.js @@ -173,11 +173,19 @@ router.post('/', async(req, res) => { /* list */ router.get('/', async(req, res) => { const logger = req.app.locals.logger; + const {page, page_size, name} = req.query || {}; + const isPaginationRequest = page !== null && page !== undefined; try { const service_provider_sid = req.user.hasServiceProviderAuth ? req.user.service_provider_sid : null; const account_sid = req.user.hasAccountAuth ? req.user.account_sid : null; - const name = req.query.name; - const results = await Application.retrieveAll(service_provider_sid, account_sid, name); + let results = []; + let total = 0; + if (isPaginationRequest) { + total = await Application.countAll({service_provider_sid, account_sid, name}); + } + results = await Application.retrieveAll({ + service_provider_sid, account_sid, name, page, page_size + }); const ret = results.map((a) => { if (a.env_vars) { a.env_vars = JSON.parse(decrypt(a.env_vars)); @@ -186,7 +194,13 @@ router.get('/', async(req, res) => { return a; } }); - res.status(200).json(ret); + const body = isPaginationRequest ? { + total, + page: Number(page), + page_size: Number(page_size), + data: ret + } : ret; + res.status(200).json(body); } catch (err) { sysError(logger, res, err); } diff --git a/lib/routes/api/phone-numbers.js b/lib/routes/api/phone-numbers.js index 2856dbf..8180248 100644 --- a/lib/routes/api/phone-numbers.js +++ b/lib/routes/api/phone-numbers.js @@ -97,28 +97,30 @@ decorate(router, PhoneNumber, ['add', 'update', 'delete'], preconditions); /* list */ router.get('/', async(req, res) => { const logger = req.app.locals.logger; - const {account_sid, filter} = req.query; + const {account_sid: query_account_sid, filter, page, page_size} = req.query; + const isPaginationRequest = page !== null && page !== undefined; + let service_provider_sid = null, account_sid = query_account_sid; + if (req.user.hasAccountAuth) { + account_sid = req.user.account_sid; + } else if (req.user.hasServiceProviderAuth) { + service_provider_sid = req.user.service_provider_sid; + } try { - let results = []; - if (req.user.hasAccountAuth) { - results = await PhoneNumber.retrieveAllByCriteria({ - account_sid: req.user.account_sid, - filter - }); - } else if (req.user.hasServiceProviderAuth) { - results = await PhoneNumber.retrieveAllByCriteria({ - service_provider_sid: req.user.service_provider_sid, - account_sid, - filter - }); - } else if (req.user.hasAdminAuth) { - results = await PhoneNumber.retrieveAllByCriteria({ - account_sid, - filter - }); + let total = 0; + if (isPaginationRequest) { + total = await PhoneNumber.countAll({service_provider_sid, account_sid, filter}); } + const results = await PhoneNumber.retrieveAllByCriteria({ + service_provider_sid, account_sid, filter, page, page_size + }); + const body = isPaginationRequest ? { + total, + page: Number(page), + page_size: Number(page_size), + data: results, + } : results; - res.status(200).json(results); + res.status(200).json(body); } catch (err) { sysError(logger, res, err); } diff --git a/lib/routes/api/service-providers.js b/lib/routes/api/service-providers.js index 266bee7..1e1c295 100644 --- a/lib/routes/api/service-providers.js +++ b/lib/routes/api/service-providers.js @@ -149,13 +149,30 @@ router.get('/:sid/VoipCarriers', async(req, res) => { try { await validateRetrieve(req); const service_provider_sid = parseServiceProviderSid(req); - const carriers = await VoipCarrier.retrieveAllForSP(service_provider_sid); - - if (req.user.hasScope('account')) { - return res.status(200).json(carriers.filter((c) => c.account_sid === req.user.account_sid || !c.account_sid)); + const {account_sid: query_account_sid, name, page, page_size} = req.query || {}; + const isPaginationRequest = page !== null && page !== undefined; + const account_sid = req.user.hasAccountAuth ? req.user.account_sid : query_account_sid || null; + let carriers = []; + let total = 0; + if (isPaginationRequest) { + total = await VoipCarrier.countAll({service_provider_sid, account_sid, name}); } + carriers = await VoipCarrier.retrieveByCriteria({ + service_provider_sid, + account_sid, + name, + page, + page_size, + }); - res.status(200).json(carriers); + const body = isPaginationRequest ? { + total, + page: Number(page), + page_size: Number(page_size), + data: carriers, + } : carriers; + + res.status(200).json(body); } catch (err) { sysError(logger, res, err); } diff --git a/lib/routes/api/voip-carriers.js b/lib/routes/api/voip-carriers.js index 9398821..f1e8753 100644 --- a/lib/routes/api/voip-carriers.js +++ b/lib/routes/api/voip-carriers.js @@ -73,22 +73,36 @@ decorate(router, VoipCarrier, ['add', 'update', 'delete'], preconditions); /* list */ router.get('/', async(req, res) => { const logger = req.app.locals.logger; - const {lookupAccountBySid} = req.app.locals; - + const {account_sid: query_account_sid, name, page, page_size} = req.query || {}; + const isPaginationRequest = page !== null && page !== undefined; + let service_provider_sid = null, account_sid = query_account_sid; + if (req.user.hasAccountAuth) { + account_sid = req.user.account_sid; + } else if (req.user.hasServiceProviderAuth) { + service_provider_sid = req.user.service_provider_sid; + } try { - let results = []; - if (req.user.hasAdminAuth) { - results = await VoipCarrier.retrieveAll(req.user.hasAccountAuth ? req.user.account_sid : null); - } else { - const account = req.user.service_provider_sid ? req.user : await lookupAccountBySid(req.user.account_sid); - results = await VoipCarrier.retrieveAllForSP(account.service_provider_sid); + let total = 0; + if (isPaginationRequest) { + total = await VoipCarrier.countAll({service_provider_sid, account_sid, name}); } - if (req.user.hasScope('account')) { - return res.status(200).json(results.filter((c) => c.account_sid === req.user.account_sid || !c.account_sid)); - } + const carriers = await VoipCarrier.retrieveByCriteria({ + service_provider_sid, + account_sid, + name, + page, + page_size, + }); - res.status(200).json(results); + const body = isPaginationRequest ? { + total, + page: Number(page), + page_size: Number(page_size), + data: carriers, + } : carriers; + + res.status(200).json(body); } catch (err) { sysError(logger, res, err); }