diff --git a/src/client/actions/RegistryUserActions.js b/src/client/actions/RegistryUserActions.js index a05e7b9a..e4254412 100644 --- a/src/client/actions/RegistryUserActions.js +++ b/src/client/actions/RegistryUserActions.js @@ -13,7 +13,7 @@ export function getRegistryUserActions(alt, registryUserResource, errorActions) loadRegistryUserList() { return dispatch => { dispatch(); - registryUserResource.findAll() + registryUserResource.findAll('includePresence=true') .then(registryUserList => this.registryUserListUpdated(registryUserList), err => errorActions.error(err, 'Käyttäjiä ei voitu ladata')); }; diff --git a/src/client/components/RegistryUserListPage/RegistryUserTable.jsx b/src/client/components/RegistryUserListPage/RegistryUserTable.jsx index 9bc83847..ed311e92 100644 --- a/src/client/components/RegistryUserListPage/RegistryUserTable.jsx +++ b/src/client/components/RegistryUserListPage/RegistryUserTable.jsx @@ -1,5 +1,6 @@ import React from 'react'; import { Table, Button } from 'react-bootstrap'; +import { Presence } from '../../components'; const RegistryUserRow = props => { const { @@ -15,6 +16,7 @@ const RegistryUserRow = props => { phoneNumber, email, status, + presence, } = registryUser; const blockStatusToggleButton = status === 'blocked' @@ -23,6 +25,7 @@ const RegistryUserRow = props => { return (
Tila | Nimi | Jäsennumero | Puhelinnumero | diff --git a/src/common/models/participant.js b/src/common/models/participant.js index c9a8da59..3d680780 100644 --- a/src/common/models/participant.js +++ b/src/common/models/participant.js @@ -314,19 +314,15 @@ export default function (Participant) { Participant.getParticipantInformationForApp = (memberNumber, email, cb) => { const findParticipant = Promise.promisify(Participant.findOne, { context: Participant }); - let err; - let where; - - if (!memberNumber && !email) { - err = new Error('email or memberNumber is required!'); - err.status = 400; - return cb(err); - } else { - if (memberNumber) { - where = { memberNumber: memberNumber }; - } else if (email) { - where = { email: email }; + Promise.try(() => { + if (!(memberNumber || email)) { + const err = new Error('email or memberNumber is required!'); + err.status = 400; + throw err; } + + return memberNumber ? { memberNumber: memberNumber } : { email: email }; + }).then(where => findParticipant({ where: where, fields: [ @@ -341,17 +337,26 @@ export default function (Participant) { 'memberNumber', 'email', ], - }).then(participant => { - if (!participant) { - err = new Error('Participant not found'); - err.status = 404; - throw err; - } else { - return participant; - } - }).asCallback(cb); + } + )).then(participant => { + if (!participant) { + const err = new Error('Participant not found'); + err.status = 404; + throw err; + } else { + return participant; + } + }).asCallback(cb); + }; + + Participant.participantAmount = (subCamp, cb) => { + const countParticipants = Promise.promisify(Participant.count, { context: Participant }); + const filter = { presence: 3 }; + if (subCamp) { + filter.subCamp = subCamp; } + countParticipants(filter).asCallback(cb); }; Participant.remoteMethod('massAssignField', @@ -376,4 +381,12 @@ export default function (Participant) { returns: { type: 'object', root: true }, } ); + + Participant.remoteMethod('participantAmount', + { + http: { path: '/participantAmount', verb: 'get' }, + accepts: { arg: 'subCamp', type: 'string', required: false }, + returns: { arg: 'amount', type: 'string' }, + } + ); } diff --git a/src/common/models/participant.json b/src/common/models/participant.json index 431c4a60..70a4b2a6 100644 --- a/src/common/models/participant.json +++ b/src/common/models/participant.json @@ -483,6 +483,13 @@ "principalId": "registryUser", "permission": "DENY", "property": "getParticipantInformationForApp" + }, + { + "accessType": "EXECUTE", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW", + "property": "participantAmount" } ], "methods": {} diff --git a/src/common/models/registryuser.js b/src/common/models/registryuser.js index bbf3b236..ff45aaa4 100644 --- a/src/common/models/registryuser.js +++ b/src/common/models/registryuser.js @@ -2,6 +2,7 @@ import app from '../../server/server.js'; import Promise from 'bluebird'; import loopback from 'loopback'; import crypto from 'crypto'; +import _ from 'lodash'; export default function(Registryuser) { Registryuser.afterRemote('create', (ctx, registryuserInstance, next) => { @@ -61,6 +62,30 @@ export default function(Registryuser) { updateRegistryUser({ id: userId }, { status: null }).asCallback(callback); }; + Registryuser.afterRemote('find', (ctx, instance, next) => { + Promise.try(() => { + const includePresence = ctx && ctx.result && ctx.req && ctx.req.query && ctx.req.query.includePresence; + if (includePresence) { + const findParticipants = Promise.promisify(app.models.Participant.find, { context: app.models.Participant }); + + const memberNumbers = ctx.result.map(user => user.memberNumber); + + // To object is an undocumented function of loopback models, which is used here to force the model object into a plain javascript object. + // Without the call setting a new property on the objects below wouldn't work. + const keyedUsers = _.keyBy(ctx.result.map(user => user.toObject()), 'memberNumber'); + + return findParticipants({ where: { memberNumber: { inq: memberNumbers } } }) + .each(participant => { + const user = keyedUsers[participant.memberNumber]; + if (user) { + user.presence = participant.presence; + } + }) + .tap(() => ctx.result = _.values(keyedUsers)); + } + }).asCallback(next); + }); + Registryuser.remoteMethod('block', { http: { path: '/:id/block', verb: 'post' }, diff --git a/test/acl-test.js b/test/acl-test.js index d69a060c..2baeb429 100644 --- a/test/acl-test.js +++ b/test/acl-test.js @@ -308,6 +308,7 @@ describe('http api access control', () => { it('appInformation: UNAUTHORIZED', () => get('/api/participants/appInformation?memberNumber=1234').expect(UNAUTHORIZED)); it('massedit: UNAUTHORIZED', () => post('/api/participants/massAssign', { ids: [1], newValue: 1, fieldName: 'presence' }).expect(UNAUTHORIZED)); + it('participantAmount: OK', () => get('/api/participants/participantAmount').expect(OK)); }); describe('Authenticated user without roles', () => { @@ -325,6 +326,7 @@ describe('http api access control', () => { it('appInformation: UNAUTHORIZED', () => get('/api/participants/appInformation?memberNumber=1234', noRolesAccessToken).expect(UNAUTHORIZED)); it('massedit: UNAUTHORIZED', () => post('/api/participants/massAssign', { ids: [1], newValue: 1, fieldName: 'presence' }, noRolesAccessToken).expect(UNAUTHORIZED)); + it('participantAmount: OK', () => get('/api/participants/participantAmount', noRolesAccessToken).expect(OK)); }); describe('registryUser', () => { @@ -342,6 +344,7 @@ describe('http api access control', () => { it('appInformation: UNAUTHORIZED', () => get('/api/participants/appInformation?memberNumber=1234', registryUserAccessToken).expect(UNAUTHORIZED)); it('massedit: UNAUTHORIZED', () => post('/api/participants/massAssign', { ids: [1], newValue: 1, fieldName: 'presence' }, registryUserAccessToken).expect(OK)); + it('participantAmount: OK', () => get('/api/participants/participantAmount', registryUserAccessToken).expect(OK)); }); describe('registryAdmin', () => { @@ -359,6 +362,7 @@ describe('http api access control', () => { it('appInformation: UNAUTHORIZED', () => get('/api/participants/appInformation?memberNumber=1234', registryAdminAccessToken).expect(UNAUTHORIZED)); it('massedit: UNAUTHORIZED', () => post('/api/participants/massAssign', { ids: [1], newValue: 1, fieldName: 'presence' }, registryAdminAccessToken).expect(UNAUTHORIZED)); + it('participantAmount: OK', () => get('/api/participants/participantAmount', registryAdminAccessToken).expect(OK)); }); describe('roihuapp user', () => { @@ -376,6 +380,7 @@ describe('http api access control', () => { it('appInformation: OK', () => get('/api/participants/appInformation?memberNumber=1234', roihuappUserAccessToken).expect(OK)); it('massedit: UNAUTHORIZED', () => post('/api/participants/massAssign', { ids: [1], newValue: 1, fieldName: 'presence' }, roihuappUserAccessToken).expect(UNAUTHORIZED)); + it('participantAmount: OK', () => get('/api/participants/participantAmount', roihuappUserAccessToken).expect(OK)); }); }); diff --git a/test/participant-amount-endpoint.test.js b/test/participant-amount-endpoint.test.js new file mode 100644 index 00000000..4462ccb6 --- /dev/null +++ b/test/participant-amount-endpoint.test.js @@ -0,0 +1,84 @@ +import app from '../src/server/server'; +import request from 'supertest-as-promised'; +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import * as testUtils from './utils/test-utils'; +import { resetDatabase } from '../scripts/seed-database'; + +const expect = chai.expect; +chai.use(chaiAsPromised); + +describe('Participant amount endpoint test', () => { + const testParticipants = [ + { + 'participantId': 1, + 'firstName': 'Teemu', + 'lastName': 'Testihenkilö', + 'nonScout': false, + 'internationalGuest': false, + 'localGroup': 'Testilippukunta', + 'campGroup': 'Leirilippukunta', + 'village': 'Testikylä', + 'subCamp': 'Humina', + 'ageGroup': 'sudenpentu', + 'memberNumber': 123, + 'presence': 3, + }, + { + 'participantId': 2, + 'firstName': 'Tero', + 'lastName': 'Esimerkki', + 'nonScout': false, + 'internationalGuest': false, + 'localGroup': 'Testilippukunta', + 'campGroup': 'Leirilippukunta', + 'village': 'Testikylä', + 'subCamp': 'Hurma', + 'ageGroup': 'sudenpentu', + 'memberNumber': 345, + 'presence': 3, + }, + { + 'participantId': 3, + 'firstName': 'Jussi', + 'lastName': 'Jukola', + 'nonScout': false, + 'internationalGuest': false, + 'localGroup': 'Testilippukunta', + 'campGroup': 'Leirilippukunta', + 'village': 'Testikylä', + 'subCamp': 'Humina', + 'ageGroup': 'seikkailija', + 'memberNumber': 859, + 'presence': 2, + }, + ]; + + beforeEach(() => + resetDatabase() + .then(() => testUtils.createFixture('Participant', testParticipants)) + ); + + function getParticipantAmount(subCamp) { + const req = request(app) + .get(`/api/Participants/participantAmount`); + if (subCamp) { + req.set('subCamp', subCamp); + } + return req; + } + + it('should give amount of present participants in whole camp', () => + getParticipantAmount().then(res => { + expect(200); + expect(res.body).to.have.property('amount', 2); + }) + ); + + it('should give amount of present participants in one sub camp', () => + getParticipantAmount('Humina').then(res => { + expect(200); + expect(res.body).to.have.property('amount', 1); + }) + ); +});
---|