From 8271d6675a1977845f9ec63597dad279fcf9e1c2 Mon Sep 17 00:00:00 2001 From: AndresMRM Date: Fri, 31 Jan 2020 10:04:07 -0300 Subject: [PATCH] Add API enpoints to create, delete and get lists. --- client/src/account/API.js | 103 ++++++++++++++++++++++++++++++++++++++ locales/en-US/common.json | 5 ++ server/models/lists.js | 27 ++++++++++ server/routes/api.js | 52 +++++++++++++++++++ 4 files changed, 187 insertions(+) diff --git a/client/src/account/API.js b/client/src/account/API.js index c1198755..4f0a889f 100644 --- a/client/src/account/API.js +++ b/client/src/account/API.js @@ -342,6 +342,109 @@ export default class API extends Component {
curl -XGET '{getUrl(`api/lists/test@example.com?access_token=${accessToken}`)}'
+

GET /api/lists-by-namespace/:namespaceId – {t('getListsInNamespace')}

+ +

+ {t('retrieveTheListsThatTheNamespaceHas')} +

+ +

+ {t('Query params')} +

+ + +

+ {t('example')} +

+ +
curl -XGET '{getUrl(`api/lists-by-namespace/1?access_token=${accessToken}`)}'
+ + +

POST /api/lists – {t('createList')}

+ +

+ {t('createListDescription')} +

+ +

+ {t('Query params')} +

+ + +

+ POST {t('arguments')} +

+ + +

+ {t('example')} +

+ +
curl -XPOST '{getUrl(`api/list?access_token=${accessToken}`)}' \
+ -d 'NAMESPACE=1' \
+ -d 'UNSUBSCRIPTION_MODE=0' \
+ -d 'NAME=list1' \
+ -d 'DESCRIPTION=a very nice list' \
+ -d 'CONTACT_EMAIL=test@example.com' \
+ -d 'HOMEPAGE=example.com' \
+ -d 'FIELDWIZARD=first_last_name' \
+ -d 'SEND_CONFIGURATION=1' \
+ -d 'PUBLIC_SUBSCRIBE=1' \
+ -d 'LISTUNSUBSCRIBE_DISABLED=0' +
+ + +

DELETE /api/lists/:listId – {t('deleteList')}

+ +

+ {t('deleteListDescription')} +

+ +

+ {t('Query params')} +

+ + +

+ {t('example')} +

+ +
curl -XDELETE '{getUrl(`api/list/B16uVTdW?access_token=${accessToken}`)}'
+ +

GET /api/rss/fetch/:campaignCid – {t('triggerFetchOfACampaign')}

diff --git a/locales/en-US/common.json b/locales/en-US/common.json index 81cb40b0..74a96dbb 100644 --- a/locales/en-US/common.json +++ b/locales/en-US/common.json @@ -72,6 +72,8 @@ "thisApiCallEitherDeleteEmailsFrom": "This API call either delete emails from blacklist", "getTheListsAUserHasSubscribedTo": "Get the lists a user has subscribed to", "retrieveTheListsThatTheUserWithEmailHas": "Retrieve the lists that the user with :email has subscribed to.", + "getListsInNamespace": "Get the lists in a namespace", + "retrieveTheListsThatTheNamespaceHas": "Retrieve the lists that the namespace with :namespaceId has.", "triggerFetchOfACampaign": "Trigger fetch of a campaign", "forcesTheRssFeedCheckToImmediatelyCheck": "Forces the RSS feed check to immediately check the campaign with the given CID (in :campaignCid). It works only for RSS campaigns.", "sendTransactionalEmail": "Send transactional email", @@ -378,6 +380,9 @@ "listDeleted": "List deleted", "editList": "Edit List", "createList": "Create List", + "deleteList": "Delete List", + "createListDescription": "Creates a new list of subscribers.", + "deleteListDescription": "Deletes a list of subscribers.", "thisIsTheListIdDisplayedToTheSubscribers": "This is the list ID displayed to the subscribers", "contactEmail": "Contact email", "contactEmailUsedInSubscriptionFormsAnd": "Contact email used in subscription forms and emails that are sent out. If not filled in, the admin email from the global settings will be used.", diff --git a/server/models/lists.js b/server/models/lists.js index 1095a2b8..e0ae81ec 100644 --- a/server/models/lists.js +++ b/server/models/lists.js @@ -121,6 +121,32 @@ async function getByCid(context, cid) { }); } +async function getByNamespaceIdTx(tx, context, namespaceId) { + // FIXME - this methods is rather suboptimal if there are many lists. It quite needs permission caching in shares.js + + const rows = await tx('lists').where('namespace', namespaceId); + await shares.enforceEntityPermissionTx(tx, context, 'namespace', namespaceId, 'view'); + + const allowed = []; + + for (const list of rows) { + try { + await shares.enforceEntityPermissionTx(tx, context, 'list', list.id, 'view'); + } catch(e) { + continue + } + allowed.push(list); + } + + return allowed; +} + +async function getByNamespaceId(context, namespaceId) { + return await knex.transaction(async tx => { + return getByNamespaceIdTx(tx, context, namespaceId); + }); +} + async function _validateAndPreprocess(tx, entity) { await namespaceHelpers.validateEntity(tx, entity); enforce(entity.unsubscription_mode >= UnsubscriptionMode.MIN && entity.unsubscription_mode <= UnsubscriptionMode.MAX, 'Unknown unsubscription mode'); @@ -283,6 +309,7 @@ module.exports.getById = getById; module.exports.getByIdWithListFields = getByIdWithListFields; module.exports.getByCidTx = getByCidTx; module.exports.getByCid = getByCid; +module.exports.getByNamespaceId = getByNamespaceId; module.exports.create = create; module.exports.updateWithConsistencyCheck = updateWithConsistencyCheck; module.exports.remove = remove; diff --git a/server/routes/api.js b/server/routes/api.js index 5bce9a5e..caeb26f2 100644 --- a/server/routes/api.js +++ b/server/routes/api.js @@ -140,6 +140,7 @@ router.postAsync('/delete/:listCid', passport.loggedIn, async (req, res) => { }); +// TODO: document endpoint router.getAsync('/subscriptions/:listCid', passport.loggedIn, async (req, res) => { const list = await lists.getByCid(req.context, req.params.listCid); const start = parseInt(req.query.start || 0, 10); @@ -167,6 +168,57 @@ router.getAsync('/lists/:email', passport.loggedIn, async (req, res) => { }); }); +// get lists by namespace +router.getAsync( + "/lists-by-namespace/:namespaceId", + passport.loggedIn, + async (req, res) => { + const _lists = await lists.getByNamespaceId( + req.context, + castToInteger(req.params.namespaceId), + ); + + res.status(200); + res.json({ + data: _lists.map(l => ({id: l.id, cid: l.cid, name: l.name})) + }); + } +); + +// create list +router.postAsync('/list', passport.loggedIn, async (req, res) => { + const input = {}; + Object.keys(req.body).forEach(key => { + input[(key || '').toString().trim().toLowerCase()] = (req.body[key] || '').toString().trim(); + }); + + if (input.fieldwizard) { + input.fieldWizard = input.fieldwizard + delete input.fieldwizard + } + + if (!input.namespace) { + throw new APIError('Missing namespace', 400); + } + + var id = await lists.create(req.context, input); + + var list = await lists.getById(req.context, id) + + res.status(200); + res.json({ + data: {id: list.cid} + }); +}); + +// delete list +router.deleteAsync('/list/:listCid', passport.loggedIn, async (req, res) => { + const list = await lists.getByCid(req.context, req.params.listCid); + await lists.remove(req.context, list.id); + + res.status(200); + res.json({}); +}); router.postAsync('/field/:listCid', passport.loggedIn, async (req, res) => { const list = await lists.getByCid(req.context, req.params.listCid);