diff --git a/database/migrations/20210000000014-create-survey-excerpts-table.js b/database/migrations/20210000000014-create-survey-excerpts-table.js new file mode 100644 index 0000000000000000000000000000000000000000..cdf8c32ca0bb284fe7c69e9f2b7d89ae0e066964 --- /dev/null +++ b/database/migrations/20210000000014-create-survey-excerpts-table.js @@ -0,0 +1,66 @@ +module.exports = { + up: (migration, Types) => { + return migration.createTable('survey_excerpts', { + id: { + type: Types.UUID, + primaryKey: true, + defaultValue: Types.UUIDV4, + allowNull: false, + }, + survey_id: { + type: Types.UUID, + allowNull: false, + references: { + model: 'surveys', + key: 'id', + }, + }, + shortcode: { + type: Types.TEXT, + allowNull: false, + }, + name: { + type: Types.TEXT, + allowNull: false, + }, + view_name: { + type: Types.TEXT, + allowNull: false, + }, + parameters: { + type: Types.JSONB, + defaultValue: {}, + allowNull: false, + }, + filters: { + type: Types.JSONB, + defaultValue: {}, + allowNull: false, + }, + meta: { + type: Types.JSONB, + defaultValue: {}, + allowNull: false, + }, + created_at: { + type: Types.DATE, + defaultValue: Types.fn('now'), + allowNull: false, + }, + updated_at: { + type: Types.DATE, + defaultValue: Types.fn('now'), + allowNull: false, + }, + deleted_at: { + type: Types.DATE, + defaultValue: null, + allowNull: true, + }, + }) + }, + + down: (migration, Types) => { + return migration.dropTable('survey_excerpts') + }, +} diff --git a/src/database/models/Survey.js b/src/database/models/Survey.js index dbe253de1053f673195a60087c8d68261d072254..bca039b0e7ddccea11b3c2497280e516d3514d16 100644 --- a/src/database/models/Survey.js +++ b/src/database/models/Survey.js @@ -4,6 +4,7 @@ const BaseModel = require('./BaseModel') class Survey extends BaseModel { static associate(models) { this.hasMany(models.SurveyUser, { foreignKey: 'survey_id' }) + this.hasMany(models.SurveyExcerpt, { foreignKey: 'survey_id' }) this.belongsToMany(models.User, { through: models.SurveyUser, timestamps: false }) } diff --git a/src/database/models/SurveyExcerpt.js b/src/database/models/SurveyExcerpt.js new file mode 100644 index 0000000000000000000000000000000000000000..a46bb472afa308fd4294e31d701915fe47c49721 --- /dev/null +++ b/src/database/models/SurveyExcerpt.js @@ -0,0 +1,65 @@ +const timestamps = require('./properties/timestamps') +const BaseModel = require('./BaseModel') + +class SurveyExcerpt extends BaseModel { + static associate(models) { + this.belongsTo(models.Survey, { foreignKey: 'survey_id' }) + } + + toJSON() { + return { + id: this.id, + name: this.name, + shortcode: this.shortcode, + meta: this.meta, + } + } +} + +module.exports = (sequelize, DataTypes) => { + SurveyExcerpt.init( + Object.assign( + { + id: { + type: DataTypes.UUID, + primaryKey: true, + defaultValue: DataTypes.UUIDV4, + validate: { + isUUID: 4, + }, + }, + shortcode: { + type: DataTypes.TEXT, + allowNull: false, + }, + view_name: { + type: DataTypes.TEXT, + allowNull: false, + }, + parameters: { + type: DataTypes.JSONB, + defaultValue: {}, + allowNull: false, + }, + filters: { + type: DataTypes.JSONB, + defaultValue: {}, + allowNull: false, + }, + meta: { + type: DataTypes.JSONB, + defaultValue: {}, + allowNull: false, + }, + }, + timestamps(DataTypes), + ), + { + sequelize, + paranoid: true, + tableName: 'survey_excerpts', + }, + ) + + return SurveyExcerpt +} diff --git a/src/domain/auth/OAuthFlow.js b/src/domain/auth/OAuthFlow.js index 0a11e47442e31c0e9ec6e2c6325dfb69c463010f..ce25c979f4e629ce78b374d5feb6cacd34ac6016 100644 --- a/src/domain/auth/OAuthFlow.js +++ b/src/domain/auth/OAuthFlow.js @@ -158,7 +158,7 @@ const scopeDescriptionMap = exports.validScopes = { icon: 'openid', name: 'Open ID Connect', description: - 'Use your Jetsam account to log in to a third party services. This will provide the third party with your name (if provided), your email address and your Jetsam account ID.' + 'Use your Jetsam app account to log in to another app or website. This will provide the operator with your name and email address.' }, 'metrics:create': { name: 'Create Metrics', diff --git a/src/http/controllers/api/content.js b/src/http/controllers/api/content.js index 1f126df9f4a691a6499f3268974f72223aad764c..0bf47a95f53516a8ea61e1641e83ed85de54619b 100644 --- a/src/http/controllers/api/content.js +++ b/src/http/controllers/api/content.js @@ -98,38 +98,50 @@ function pointFromString(str) { } } +function bufferFromCorners(pointFrom, pointTo) { + const fromPoint = pointFromString(pointFrom) + const toPoint = pointFromString(pointTo) + + const minFromLong = Math.min(fromPoint.longitude, toPoint.longitude) + const maxFromLong = Math.max(fromPoint.longitude, toPoint.longitude) + const minFromLat = Math.min(fromPoint.latitude, toPoint.latitude) + const maxFromLat = Math.max(fromPoint.latitude, toPoint.latitude) + + return [ + [minFromLong, minFromLat], + [minFromLong, maxFromLat], + [maxFromLong, maxFromLat], + [maxFromLong, minFromLat], + [minFromLong, minFromLat], + ] +} + +function bufferFromPolygon(poly) { + return poly.split(';') + .map(s => s.trim()) + .filter(Boolean) + .map(s => pointFromString(s)) +} + exports.getWithin = async ctx => { const { point_from, point_to, - date_from, + within, + date_from = moment.utc().subtract(30, 'days'), date_to = moment.utc(), types = '', format = 'full', } = ctx.request.query - const fromPoint = pointFromString(point_from) - const toPoint = pointFromString(point_to) + const pointBufferData = within ? bufferFromPolygon(within) : bufferFromCorners(point_from, point_to) + const pointBuffer = pointBufferData.map(pb => pb.map(Number).join(' ')).join(',') const fromDate = moment.utc(date_from) const toDate = moment.utc(date_to) const metricTypes = splitString(types) - const minFromLong = Math.min(fromPoint.longitude, toPoint.longitude) - const maxFromLong = Math.max(fromPoint.longitude, toPoint.longitude) - const minFromLat = Math.min(fromPoint.latitude, toPoint.latitude) - const maxFromLat = Math.max(fromPoint.latitude, toPoint.latitude) - - const pointBuffer = [ - [minFromLong, minFromLat], - [minFromLong, maxFromLat], - [maxFromLong, maxFromLat], - [maxFromLong, minFromLat], - [minFromLong, minFromLat], - ] - .map(pb => pb.map(Number).join(' ')) - .join(',') const query = format === 'marker' @@ -139,8 +151,8 @@ exports.getWithin = async ctx => { const metrics = await query( pointBuffer, metricTypes, - // fromDate.toISOString(), - moment().subtract(12, 'months').toISOString(), + fromDate.toISOString(), + // moment().subtract(12, 'months').toISOString(), toDate.toISOString(), ) diff --git a/src/http/controllers/api/v2/surveys.js b/src/http/controllers/api/v2/surveys.js index 07127a5e9b9db3bed7fb359bd2f9ac3ed251092f..24dce09034d3fd3178db1b0dc49e46ce068c4dbe 100644 --- a/src/http/controllers/api/v2/surveys.js +++ b/src/http/controllers/api/v2/surveys.js @@ -51,6 +51,43 @@ exports.list = async ctx => { } } +exports.listExcerpts = async ctx => { + const {Sequelize, SurveyExcerpt} = require('database/models') + const user = await ctx.services['core.auth'].getUser() + + if (!user) { + throw new UnauthorizedError() + } + + const surveys = await user.getSurveys({ include: [SurveyExcerpt] }) + + ctx.body = { + excerpts: surveys.flatMap(s => s.SurveyExcerpts.map(se => se.toJSON())) + } +} + +exports.getExcerpt = async ctx => { + if (!ctx.models.excerpt) { + throw new NotFoundError('Survey Excerpt') + } + + const { view_name } = ctx.models.excerpt + try { + const result = await sequelize.query(`SELECT * FROM ${ view_name }`, { + type: QueryTypes.SELECT, + }) + + ctx.body = { + data: result, + } + } catch(e) { + console.log(e) + ctx.body = { + data: null + } + } +} + exports.joined = async ctx => { const user = await ctx.services['core.auth'].getUser() if (user == null) { diff --git a/src/http/params/survey_excerpt.js b/src/http/params/survey_excerpt.js new file mode 100644 index 0000000000000000000000000000000000000000..103e2447b4d28d5591b6f16c46748576fe001c51 --- /dev/null +++ b/src/http/params/survey_excerpt.js @@ -0,0 +1,7 @@ +const { SurveyExcerpt, Sequelize} = require('database/models') + +module.exports = async (id, ctx, next) => { + ctx.models = ctx.models ?? {} + ctx.models.excerpt = await SurveyExcerpt.findByPk(id) + return await next() +} diff --git a/src/http/routers/routes_v2.js b/src/http/routers/routes_v2.js index fc6db7be393b145036e43450caeab180fad6e7b6..8f9ea777a90c90e011b6e8be169892d069615d0f 100644 --- a/src/http/routers/routes_v2.js +++ b/src/http/routers/routes_v2.js @@ -51,8 +51,11 @@ router.delete('/uploads/:upload_id', noop) router.put('/uploads/:upload_id/:property', noop) router.param('survey', param('survey')) +router.param('excerpt', param('survey_excerpt')) router.get('/surveys', controller('api/v2/surveys', 'list')) +router.get('/excerpts', controller('api/v2/surveys', 'listExcerpts')) +router.get('/excerpts/:excerpt', controller('api/v2/surveys', 'getExcerpt')) router.get('/surveys/:survey', controller('api/v2/surveys', 'get')) router.post('/surveys/:survey/membership', controller('api/v2/surveys', 'join')) router.delete('/surveys/:survey/membership', controller('api/v2/surveys', 'leave')) @@ -62,54 +65,4 @@ if (config('app.dev')) { router.post('/an/ev', controller('api/analytics', 'track')) -// api.post('/metrics', controller('api/content', 'postMetric')) -// api.get('/metrics', controller('api/content', 'getWithin')) -// -// api.get('/images', controller('api/storage', 'getFiles')) -// api.post( -// '/images', -// upload.single('featured_image'), -// controller('api/storage', 'saveFile'), -// ) -// api.post('/images/:imageId/feature', controller('api/storage', 'featureImage')) -// -// /** @deprecated */ -// api.post( -// '/feature', -// upload.single('featured_image'), -// controller('api/storage', 'saveFile'), -// ) -// -// api.get('/feed', controller('api/storage', 'feed')) -// api.post('/feed/:fileId/like', controller('api/storage', 'like')) -// api.post('/feed/:fileId/unlike', controller('api/storage', 'unlike')) -// -// api.post('/register', controller('api/auth', 'register')) -// api.post('/login', controller('api/auth', 'login')) -// -// api.post('/auth/reset-token', controller('api/auth', 'triggerPasswordReset')) -// api.post('/auth/reset-password', controller('api/auth', 'handlePasswordReset')) -// -// api.param('oauthClientId', param('oauth_client')) -// -// api.get('/oauth/clients', controller('api/oauth', 'listClients')) -// api.post('/oauth/clients', controller('api/oauth', 'createClient')) -// api.post( -// '/oauth/clients/:oauthClientId/redirects', -// controller('api/oauth', 'addClientRedirect'), -// ) -// api.delete( -// '/oauth/clients/:oauthClientId/redirects', -// controller('api/oauth', 'removeClientRedirect'), -// ) -// -// api.get('/self', controller('api/user', 'self')) -// api.get('/self/bundles', controller('api/app', 'getBundles')) -// api.put('/self/:property', controller('api/user', 'updateOne')) -// -// api.post('/an/id', async ctx => {}) -// api.post('/an/ev', controller('api/analytics', 'track')) -// -// api.post('/feedback', controller('api/feedback', 'send')) - module.exports = router