diff --git a/app.js b/app.js index 49c4f40..5636665 100644 --- a/app.js +++ b/app.js @@ -22,6 +22,7 @@ const commentRouter = require('./app/routes/comment') const projectRouter = require('./app/routes/project') const notificationRouter = require('./app/routes/notification') const proposalRouter = require('./app/routes/proposal') +const analyticsRouter = require('./app/routes/analytics') const app = express() const server = require('http').Server(app) @@ -81,6 +82,7 @@ app.use('/shortUrl', shortUrlRouter) app.use('/comment', commentRouter) app.use('/project', projectRouter) app.use('/proposal', proposalRouter) +app.use('/analytics', analyticsRouter) // catch 404 and forward to error handler app.use(function (req, res, next) { diff --git a/app/controllers/analytics.js b/app/controllers/analytics.js new file mode 100644 index 0000000..adcc31f --- /dev/null +++ b/app/controllers/analytics.js @@ -0,0 +1,104 @@ +const { google } = require('googleapis') +const analytics = google.analytics('v3') +const jwt = require('../../config/gAnalytics') +const viewId = process.env.VIEW_ID +const HANDLER = require('../utils/response-helper') +const HttpStatus = require('http-status-codes') + +module.exports = { + getBrowser: async (req, res, next) => { + const { startDate, endDate, proposalId } = req.body + console.log(req.body) + try { + const result = await analytics.data.ga.get({ + auth: jwt, + ids: `ga:${viewId}`, + metrics: 'ga:users', + dimensions: ['ga:browser'], + 'start-date': startDate, + 'end-date': endDate, + filters: `ga:pagePath==/${proposalId}` + }) + res.status(HttpStatus.OK).json({ analytics: result.data.rows }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + + getCountries: async (req, res, next) => { + const { startDate, endDate, proposalId } = req.body + + try { + const result = await analytics.data.ga.get({ + auth: jwt, + ids: `ga:${viewId}`, + metrics: 'ga:users', + dimensions: ['ga:country'], + 'start-date': startDate, + 'end-date': endDate, + filters: `ga:pagePath==/${proposalId}` + }) + res.status(HttpStatus.OK).json({ analytics: result.data.rows }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + + getDevice: async (req, res, next) => { + const { startDate, endDate, proposalId } = req.body + + try { + const result = await analytics.data.ga.get({ + auth: jwt, + ids: `ga:${viewId}`, + metrics: 'ga:users', + dimensions: ['ga:deviceCategory'], + 'start-date': startDate, + 'end-date': endDate, + filters: `ga:pagePath==/${proposalId}` + }) + res.status(HttpStatus.OK).json({ analytics: result.data.rows }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + + getTopProposals: async (req, res, next) => { + const { startDate, endDate } = req.body + + try { + const result = await analytics.data.ga.get({ + auth: jwt, + ids: `ga:${viewId}`, + metrics: 'ga:pageviews', + dimensions: ['ga:pagePath'], + 'start-date': startDate, + 'end-date': endDate, + filters: 'ga:pagePath!=/homepage' + }) + res.status(HttpStatus.OK).json({ analytics: result.data }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + + getProposalViews: async (req, res, next) => { + const { startDate, endDate, proposalId } = req.body + + try { + const result = await analytics.data.ga.get({ + auth: jwt, + ids: `ga:${viewId}`, + metrics: 'ga:pageviews', + dimensions: ['ga:date'], + 'start-date': startDate, + 'end-date': endDate, + filters: `ga:pagePath==/${proposalId}` + }) + + res.status(HttpStatus.OK).json({ analytics: result.data.rows }) + } catch (error) { + HANDLER.handleError(res, error) + } + } +} diff --git a/app/routes/analytics.js b/app/routes/analytics.js new file mode 100644 index 0000000..3c84b3e --- /dev/null +++ b/app/routes/analytics.js @@ -0,0 +1,22 @@ +const express = require('express') +const router = express.Router() +const analyticsController = require('../controllers/analytics') +const auth = require('../middleware/auth') +const isUnderMaintenance = require('../middleware/maintenance') + +// Get Browser analytics +router.post('/browser', isUnderMaintenance, auth, analyticsController.getBrowser) + +// Get country analytics +router.post('/countries', isUnderMaintenance, auth, analyticsController.getCountries) + +// Get Device analytics +router.post('/device', isUnderMaintenance, auth, analyticsController.getDevice) + +// Get most viewed Proposals +router.post('/mostviewed', isUnderMaintenance, auth, analyticsController.getTopProposals) + +// Get Views of a specific proposal +router.post('/views', isUnderMaintenance, auth, analyticsController.getProposalViews) + +module.exports = router diff --git a/config/gAnalytics.js b/config/gAnalytics.js new file mode 100644 index 0000000..5da67a0 --- /dev/null +++ b/config/gAnalytics.js @@ -0,0 +1,10 @@ +const { google } = require('googleapis') +const clientEMail = process.env.CLIENT_EMAIL +const privateKey = process.env.PRIVATE_KEY +const scope = process.env.SCOPE + +module.exports = new google.auth.JWT({ + email: clientEMail, + key: privateKey, + scopes: [scope] +}) diff --git a/package-lock.json b/package-lock.json index a8ef638..e8c6084 100644 --- a/package-lock.json +++ b/package-lock.json @@ -565,6 +565,14 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -807,6 +815,11 @@ "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -1070,6 +1083,11 @@ "callsite": "1.0.0" } }, + "bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" + }, "binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", @@ -2716,6 +2734,11 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, "events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", @@ -2885,8 +2908,7 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extend-shallow": { "version": "3.0.2", @@ -3009,6 +3031,11 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fast-text-encoding": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", + "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" + }, "fast-safe-stringify": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", @@ -3779,6 +3806,64 @@ "wide-align": "^1.1.0" } }, + "gaxios": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.0.4.tgz", + "integrity": "sha512-97NmFuMETFQh6gqPUxkqjxRMjmY8aRKRMphIkgO/b90AbCt5wAVuXsp8oWjIXlLN2pIK/fsXD8edcM7ULkFMLg==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + }, + "dependencies": { + "agent-base": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", + "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", + "requires": { + "debug": "4" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "gcp-metadata": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.1.4.tgz", + "integrity": "sha512-5J/GIH0yWt/56R3dNaNWPGQ/zXsZOddYECfJaqxFWgrZ9HC2Kvc5vl9upOgUUHKzURjAVf2N+f6tEJiojqXUuA==", + "requires": { + "gaxios": "^3.0.0", + "json-bigint": "^1.0.0" + } + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -3855,6 +3940,93 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, + "google-auth-library": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.0.5.tgz", + "integrity": "sha512-Wj31lfTm2yR4g3WfOOB1Am1tt478Xq9OvzTPQJi17tn/I9R5IcsxjANBsE93nYmxYxtwDedhOdIb8l3vSPG49Q==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^3.0.0", + "gcp-metadata": "^4.1.0", + "gtoken": "^5.0.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "google-p12-pem": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.2.tgz", + "integrity": "sha512-tbjzndQvSIHGBLzHnhDs3cL4RBjLbLXc2pYvGH+imGVu5b4RMAttUTdnmW2UH0t11QeBTXZ7wlXPS7hrypO/tg==", + "requires": { + "node-forge": "^0.9.0" + } + }, + "googleapis": { + "version": "56.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-56.0.0.tgz", + "integrity": "sha512-qf8Xp7QV7h81nWzspRCMIEnFsG6aBrsomkl0H/578G8/8lIZ/tt+6yDexVTPsR6XDo/jhSb4rtN9462AmLhQxA==", + "requires": { + "google-auth-library": "^6.0.0", + "googleapis-common": "^4.4.0" + } + }, + "googleapis-common": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-4.4.0.tgz", + "integrity": "sha512-Bgrs8/1OZQFFIfVuX38L9t48rPAkVUXttZy6NzhhXxFOEMSHgfFIjxou7RIXOkBHxmx2pVwct9WjKkbnqMYImQ==", + "requires": { + "extend": "^3.0.2", + "gaxios": "^3.0.0", + "google-auth-library": "^6.0.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^8.0.0" + }, + "dependencies": { + "uuid": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.2.0.tgz", + "integrity": "sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==" + } + } + }, "got": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", @@ -3886,6 +4058,43 @@ "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", "dev": true }, + "gtoken": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.2.tgz", + "integrity": "sha512-lull70rHCTvRTmAt+R/6W5bTtx4MjHku7AwJwK5fGqhOmygcZud0nrZcX+QUNfBJwCzqy7S5i1Bc4NYnr5PMMA==", + "requires": { + "gaxios": "^3.0.0", + "google-p12-pem": "^3.0.0", + "jws": "^4.0.0", + "mime": "^2.2.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" + } + } + }, "handlebars": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz", @@ -5261,6 +5470,14 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -5887,8 +6104,12 @@ "node-fetch": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", - "dev": true + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, + "node-forge": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz", + "integrity": "sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==" }, "node-int64": { "version": "0.4.0", @@ -8154,6 +8375,11 @@ "prepend-http": "^1.0.1" } }, + "url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=" + }, "urlgrey": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/urlgrey/-/urlgrey-0.4.4.tgz", diff --git a/package.json b/package.json index 55e9bb6..f99e0e6 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "dotenv": "^8.2.0", "ejs": "~2.6.1", "express": "^4.16.4", + "googleapis": "^56.0.0", "http-status-codes": "^1.4.0", "jsonwebtoken": "^8.5.1", "moment": "^2.27.0",