From 39aa56454eecce22735e8256267036b5bf79c1f6 Mon Sep 17 00:00:00 2001 From: Kailash Nadh Date: Thu, 9 Jul 2020 23:29:55 +0530 Subject: [PATCH 1/2] Fix missing API response data{} envelope --- subscribers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/subscribers.go b/subscribers.go index c76d8d9..dda279d 100644 --- a/subscribers.go +++ b/subscribers.go @@ -76,7 +76,7 @@ func handleGetSubscriber(c echo.Context) error { return err } - return c.JSON(http.StatusOK, sub) + return c.JSON(http.StatusOK, okResp{sub}) } // handleQuerySubscribers handles querying subscribers based on an arbitrary SQL expression. @@ -214,7 +214,7 @@ func handleUpdateSubscriber(c echo.Context) error { } _ = sendOptinConfirmation(sub, []int64(req.Lists), app) - return c.JSON(http.StatusOK, sub) + return c.JSON(http.StatusOK, okResp{sub}) } // handleGetSubscriberSendOptin sends an optin confirmation e-mail to a subscriber. From 3df889cdc10371ed90ae97b171154a35e441b262 Mon Sep 17 00:00:00 2001 From: Kailash Nadh Date: Thu, 9 Jul 2020 23:29:59 +0530 Subject: [PATCH 2/2] Refactor global API response handling in axios. Instead of using a response transformer, move the global response JSON transformation (snake case to camel case) to the pre-existing response interceptor. Also, fix the `data.data` and `data` discrepancy in responses. --- frontend/src/api/index.js | 42 +++++++++++++++++---------- frontend/src/views/Campaign.vue | 31 +++++++++----------- frontend/src/views/Campaigns.vue | 10 +++---- frontend/src/views/Dashboard.vue | 14 ++++----- frontend/src/views/Forms.vue | 2 +- frontend/src/views/Import.vue | 10 +++---- frontend/src/views/ListForm.vue | 8 ++--- frontend/src/views/SubscriberForm.vue | 8 ++--- frontend/src/views/Subscribers.vue | 1 + frontend/src/views/TemplateForm.vue | 8 ++--- 10 files changed, 70 insertions(+), 64 deletions(-) diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index bb75a9d..9c7e1d1 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -9,22 +9,22 @@ const http = axios.create({ baseURL: process.env.BASE_URL, withCredentials: false, responseType: 'json', - transformResponse: [ - // Apply the defaut transformations as well. - ...axios.defaults.transformResponse, - (resp) => { - if (!resp) { - return resp; - } + // transformResponse: [ + // // Apply the defaut transformations as well. + // ...axios.defaults.transformResponse, + // (resp) => { + // if (!resp) { + // return resp; + // } - // There's an error message. - if ('message' in resp && resp.message !== '') { - return resp; - } + // // There's an error message. + // if ('message' in resp && resp.message !== '') { + // return resp; + // } - return humps.camelizeKeys(resp.data); - }, - ], + // return humps.camelizeKeys(resp.data); + // }, + // ], // Override the default serializer to switch params from becoming []id=a&[]id=b ... // in GET and DELETE requests to id=a&id=b. @@ -47,11 +47,21 @@ http.interceptors.response.use((resp) => { store.commit('setLoading', { model: resp.config.loading, status: false }); } + let data = {}; + if (resp.data && resp.data.data) { + if (typeof resp.data.data === 'object') { + data = humps.camelizeKeys(resp.data.data); + } else { + data = resp.data.data; + } + } + // Store the API response for a model. if ('store' in resp.config) { - store.commit('setModelResponse', { model: resp.config.store, data: resp.data }); + store.commit('setModelResponse', { model: resp.config.store, data }); } - return resp; + + return data; }, (err) => { // Clear the loading state for a model. if ('loading' in err.config) { diff --git a/frontend/src/views/Campaign.vue b/frontend/src/views/Campaign.vue index 12ecd22..767bc35 100644 --- a/frontend/src/views/Campaign.vue +++ b/frontend/src/views/Campaign.vue @@ -181,13 +181,13 @@ export default Vue.extend({ }, getCampaign(id) { - return this.$api.getCampaign(id).then((r) => { - this.data = r.data; - this.form = { ...this.form, ...r.data }; + return this.$api.getCampaign(id).then((data) => { + this.data = data; + this.form = { ...this.form, ...data }; - if (r.data.sendAt !== null) { + if (data.sendAt !== null) { this.form.sendLater = true; - this.form.sendAtDate = dayjs(r.data.sendAt).toDate(); + this.form.sendAtDate = dayjs(data.sendAt).toDate(); } }); }, @@ -236,13 +236,8 @@ export default Vue.extend({ // body: this.form.body, }; - this.$api.createCampaign(data).then((r) => { - this.$router.push({ name: 'campaign', hash: '#content', params: { id: r.data.id } }); - - // this.data = r.data; - // this.isEditing = true; - // this.isNew = false; - // this.activeTab = 1; + this.$api.createCampaign(data).then((d) => { + this.$router.push({ name: 'campaign', hash: '#content', params: { id: d.id } }); }); return false; }, @@ -270,10 +265,10 @@ export default Vue.extend({ // This promise is used by startCampaign to first save before starting. return new Promise((resolve) => { - this.$api.updateCampaign(this.data.id, data).then((resp) => { - this.data = resp.data; + this.$api.updateCampaign(this.data.id, data).then((d) => { + this.data = d; this.$buefy.toast.open({ - message: `'${resp.data.name}' ${typMsg}`, + message: `'${d.name}' ${typMsg}`, type: 'is-success', queue: false, }); @@ -344,10 +339,10 @@ export default Vue.extend({ } // Get templates list. - this.$api.getTemplates().then((r) => { - if (r.data.length > 0) { + this.$api.getTemplates().then((data) => { + if (data.length > 0) { if (!this.form.templateId) { - this.form.templateId = r.data.find((i) => i.isDefault === true).id; + this.form.templateId = data.find((i) => i.isDefault === true).id; } } }); diff --git a/frontend/src/views/Campaigns.vue b/frontend/src/views/Campaigns.vue index e45ed43..3210080 100644 --- a/frontend/src/views/Campaigns.vue +++ b/frontend/src/views/Campaigns.vue @@ -293,9 +293,9 @@ export default Vue.extend({ // Poll for the status as long as the import is running. this.pollID = setInterval(() => { - this.$api.getCampaignStats().then((r) => { + this.$api.getCampaignStats().then((data) => { // Stop polling. No running campaigns. - if (r.data.length === 0) { + if (data.length === 0) { clearInterval(this.pollID); // There were running campaigns and stats earlier. Clear them @@ -307,7 +307,7 @@ export default Vue.extend({ } else { // Turn the list of campaigns [{id: 1, ...}, {id: 2, ...}] into // a map indexed by the id: {1: {}, 2: {}}. - this.campaignStatsData = r.data.reduce((obj, cur) => ({ ...obj, [cur.id]: cur }), {}); + this.campaignStatsData = data.reduce((obj, cur) => ({ ...obj, [cur.id]: cur }), {}); } }, () => { clearInterval(this.pollID); @@ -336,8 +336,8 @@ export default Vue.extend({ template_id: c.templateId, body: c.body, }; - this.$api.createCampaign(data).then((r) => { - this.$router.push({ name: 'campaign', params: { id: r.data.id } }); + this.$api.createCampaign(data).then((d) => { + this.$router.push({ name: 'campaign', params: { id: d.id } }); }); }, diff --git a/frontend/src/views/Dashboard.vue b/frontend/src/views/Dashboard.vue index 90044d1..cec3163 100644 --- a/frontend/src/views/Dashboard.vue +++ b/frontend/src/views/Dashboard.vue @@ -185,31 +185,31 @@ export default Vue.extend({ mounted() { // Pull the counts. - this.$api.getDashboardCounts().then((r) => { - this.counts = r.data; + this.$api.getDashboardCounts().then((data) => { + this.counts = data; this.isCountsLoading = false; }); // Pull the charts. - this.$api.getDashboardCharts().then((r) => { + this.$api.getDashboardCharts().then((data) => { this.isChartsLoading = false; // vue-c3 lib requires unique instances of Vue() to communicate. - if (r.data.campaignViews.length > 0) { + if (data.campaignViews.length > 0) { this.chartViewsInst = this; this.$nextTick(() => { this.chartViewsInst.$emit('init', - this.makeChart('Campaign views', r.data.campaignViews)); + this.makeChart('Campaign views', data.campaignViews)); }); } - if (r.data.linkClicks.length > 0) { + if (data.linkClicks.length > 0) { this.chartClicksInst = new Vue(); this.$nextTick(() => { this.chartClicksInst.$emit('init', - this.makeChart('Link clicks', r.data.linkClicks)); + this.makeChart('Link clicks', data.linkClicks)); }); } }); diff --git a/frontend/src/views/Forms.vue b/frontend/src/views/Forms.vue index 0c55b53..e4ccd87 100644 --- a/frontend/src/views/Forms.vue +++ b/frontend/src/views/Forms.vue @@ -33,7 +33,7 @@ <p><input type="text" name="name" placeholder="Name (optional)" /></p> <p><input type="submit" value="Subscribe" /></p> diff --git a/frontend/src/views/Import.vue b/frontend/src/views/Import.vue index 8879f33..ca97785 100644 --- a/frontend/src/views/Import.vue +++ b/frontend/src/views/Import.vue @@ -216,10 +216,10 @@ export default Vue.extend({ // Poll for the status as long as the import is running. this.pollID = setInterval(() => { - this.$api.getImportStatus().then((r) => { + this.$api.getImportStatus().then((data) => { this.isProcessing = false; this.isLoading = false; - this.status = r.data; + this.status = data; this.getLogs(); if (!this.isRunning()) { @@ -236,8 +236,8 @@ export default Vue.extend({ }, getLogs() { - this.$api.getImportLogs().then((r) => { - this.logs = r.data; + this.$api.getImportLogs().then((data) => { + this.logs = data; Vue.nextTick(() => { // vue.$refs doesn't work as the logs textarea is rendered dynamiaclly. @@ -271,7 +271,7 @@ export default Vue.extend({ })); params.set('file', this.form.file); - // Make the API request. + // Post. this.$api.importSubscribers(params).then(() => { // On file upload, show a confirmation. this.$buefy.toast.open({ diff --git a/frontend/src/views/ListForm.vue b/frontend/src/views/ListForm.vue index e44f1fb..781810b 100644 --- a/frontend/src/views/ListForm.vue +++ b/frontend/src/views/ListForm.vue @@ -79,11 +79,11 @@ export default Vue.extend({ }, createList() { - this.$api.createList(this.form).then((resp) => { + this.$api.createList(this.form).then((data) => { this.$emit('finished'); this.$parent.close(); this.$buefy.toast.open({ - message: `'${resp.data.name}' created`, + message: `'${data.name}' created`, type: 'is-success', queue: false, }); @@ -91,11 +91,11 @@ export default Vue.extend({ }, updateList() { - this.$api.updateList({ id: this.data.id, ...this.form }).then((resp) => { + this.$api.updateList({ id: this.data.id, ...this.form }).then((data) => { this.$emit('finished'); this.$parent.close(); this.$buefy.toast.open({ - message: `'${resp.data.name}' updated`, + message: `'${data.name}' updated`, type: 'is-success', queue: false, }); diff --git a/frontend/src/views/SubscriberForm.vue b/frontend/src/views/SubscriberForm.vue index 0aa05a3..fc5175b 100644 --- a/frontend/src/views/SubscriberForm.vue +++ b/frontend/src/views/SubscriberForm.vue @@ -108,11 +108,11 @@ export default Vue.extend({ lists: this.form.lists.map((l) => l.id), }; - this.$api.createSubscriber(data).then((resp) => { + this.$api.createSubscriber(data).then((d) => { this.$emit('finished'); this.$parent.close(); this.$buefy.toast.open({ - message: `'${resp.data.name}' created`, + message: `'${d.name}' created`, type: 'is-success', queue: false, }); @@ -136,11 +136,11 @@ export default Vue.extend({ lists: this.form.lists.map((l) => l.id), }; - this.$api.updateSubscriber(data).then((resp) => { + this.$api.updateSubscriber(data).then((d) => { this.$emit('finished'); this.$parent.close(); this.$buefy.toast.open({ - message: `'${resp.data.name}' updated`, + message: `'${d.name}' updated`, type: 'is-success', queue: false, }); diff --git a/frontend/src/views/Subscribers.vue b/frontend/src/views/Subscribers.vue index 1ace7ce..7bb2c80 100644 --- a/frontend/src/views/Subscribers.vue +++ b/frontend/src/views/Subscribers.vue @@ -385,6 +385,7 @@ export default Vue.extend({ bulkChangeLists(action, lists) { const data = { action, + query: this.fullQueryExp, target_list_ids: lists.map((l) => l.id), }; diff --git a/frontend/src/views/TemplateForm.vue b/frontend/src/views/TemplateForm.vue index 9df3259..d9ce783 100644 --- a/frontend/src/views/TemplateForm.vue +++ b/frontend/src/views/TemplateForm.vue @@ -94,11 +94,11 @@ export default Vue.extend({ body: this.form.body, }; - this.$api.createTemplate(data).then((resp) => { + this.$api.createTemplate(data).then((d) => { this.$emit('finished'); this.$parent.close(); this.$buefy.toast.open({ - message: `'${resp.data.name}' created`, + message: `'${d.name}' created`, type: 'is-success', queue: false, }); @@ -112,11 +112,11 @@ export default Vue.extend({ body: this.form.body, }; - this.$api.updateTemplate(data).then((resp) => { + this.$api.updateTemplate(data).then((d) => { this.$emit('finished'); this.$parent.close(); this.$buefy.toast.open({ - message: `'${resp.data.name}' updated`, + message: `'${d.name}' updated`, type: 'is-success', queue: false, });