Skip to content
Commits on Source (58)
.releaserc.js
public/fonts/js/all.min.js
\ No newline at end of file
public/fonts/js/all.min.js
local_modules
\ No newline at end of file
......@@ -39,7 +39,7 @@ stages:
- !reference [.rules-map, on-dev]
- !reference [.rules-map, not-on-semantic-release-commit]
- !reference [.rules-map, on-branch]
image: hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.13.3
image: hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.15
cache:
key:
files:
......
......@@ -5,30 +5,30 @@
# but you can also edit it by hand.
meteor-base@1.5.1 # Packages every Meteor app needs to have
mobile-experience@1.1.0 # Packages for a great mobile UX
mongo@1.16.7 # The database Meteor supports right now
mobile-experience@1.1.1 # Packages for a great mobile UX
mongo@1.16.8 # The database Meteor supports right now
reactive-var@1.0.12 # Reactive variable for tracker
tracker@1.3.2 # Meteor's client-side reactive programming library
tracker@1.3.3 # Meteor's client-side reactive programming library
fourseven:scss
standard-minifier-js@2.8.1 # JS minifier run for production mode
es5-shim@4.8.0 # ECMAScript 5 compatibility for older browsers
ecmascript@0.16.7 # Enable ECMAScript2015+ syntax in app code
typescript@4.9.4 # Enable TypeScript syntax in .ts and .tsx modules
ecmascript@0.16.8 # Enable ECMAScript2015+ syntax in app code
typescript@4.9.5 # Enable TypeScript syntax in .ts and .tsx modules
shell-server@0.5.0 # Server-side component of the `meteor shell` command
zodern:melte
rdb:svelte-meteor-data
static-html@1.3.2
accounts-password@2.3.4
accounts-base@2.2.8
accounts-password@2.4.0
accounts-base@2.2.10
hot-module-replacement@0.5.3
aldeed:collection2
aldeed:schema-index
mdg:validated-method
#server-render
mexar:mdt
ddp-rate-limiter@1.2.0
ddp-rate-limiter@1.2.1
tmeasday:publish-counts
email@2.2.5
......@@ -38,3 +38,4 @@ meteortesting:mocha
dburles:factory
johanbrook:publication-collector
eoleteam:accounts-keycloak
littledata:synced-cron
accounts-base@2.2.8
accounts-oauth@1.4.2
accounts-password@2.3.4
accounts-base@2.2.10
accounts-oauth@1.4.3
accounts-password@2.4.0
aldeed:collection2@3.5.0
aldeed:schema-index@3.0.0
aldeed:schema-index@3.1.0
allow-deny@1.1.1
autoupdate@1.8.0
babel-compiler@7.10.4
babel-compiler@7.10.5
babel-runtime@1.5.1
base64@1.0.12
binary-heap@1.0.11
blaze-tools@1.1.3
boilerplate-generator@1.7.1
blaze-tools@1.1.4
boilerplate-generator@1.7.2
caching-compiler@1.2.2
caching-html-compiler@1.2.1
caching-html-compiler@1.2.2
callback-hook@1.5.1
check@1.3.2
dburles:factory@1.5.0
ddp@1.4.1
ddp-client@2.6.1
ddp-common@1.4.0
ddp-rate-limiter@1.2.0
ddp-server@2.6.2
ddp-rate-limiter@1.2.1
ddp-server@2.7.0
diff-sequence@1.1.2
dynamic-import@0.7.3
ecmascript@0.16.7
ecmascript@0.16.8
ecmascript-runtime@0.8.1
ecmascript-runtime-client@0.12.1
ecmascript-runtime-server@0.11.0
ejson@1.1.3
email@2.2.5
eoleteam:accounts-keycloak@2.1.0
eoleteam:keycloak-oauth@2.2.0
eoleteam:keycloak-oauth@2.3.0
es5-shim@4.8.0
fetch@0.1.3
fetch@0.1.4
fourseven:scss@4.16.0
geojson-utils@1.0.11
hot-code-push@1.0.4
hot-module-replacement@0.5.3
html-tools@1.1.3
htmljs@1.1.1
html-tools@1.1.4
htmljs@1.2.1
http@2.0.0
id-map@1.1.1
inter-process-messaging@0.1.1
johanbrook:publication-collector@1.1.0
launch-screen@1.3.0
launch-screen@2.0.0
littledata:synced-cron@1.5.1
localstorage@1.2.0
logging@1.3.2
logging@1.3.3
mdg:validated-method@1.3.0
meteor@1.11.3
meteor@1.11.5
meteor-base@1.5.1
meteortesting:browser-tests@1.5.3
meteortesting:mocha@2.1.0
......@@ -55,18 +56,18 @@ meteortesting:mocha-core@8.1.2
mexar:mdt@0.2.2
minifier-js@2.7.5
minimongo@1.9.3
mobile-experience@1.1.0
mobile-experience@1.1.1
mobile-status-bar@1.1.0
modern-browsers@0.1.9
modules@0.19.0
modern-browsers@0.1.10
modules@0.20.0
modules-runtime@0.13.1
modules-runtime-hot@0.14.2
mongo@1.16.7
mongo@1.16.9
mongo-decimal@0.1.3
mongo-dev-server@1.1.0
mongo-id@1.0.8
npm-mongo@4.16.0
oauth@2.2.0
npm-mongo@4.17.2
oauth@2.2.1
oauth2@1.3.2
ordered-dict@1.1.0
promise@0.12.2
......@@ -74,27 +75,27 @@ raix:eventemitter@1.0.0
random@1.2.1
rate-limit@1.1.1
rdb:svelte-meteor-data@0.3.1
react-fast-refresh@0.2.7
react-fast-refresh@0.2.8
reactive-var@1.0.12
reload@1.3.1
retry@1.1.0
routepolicy@1.1.1
service-configuration@1.3.1
service-configuration@1.3.3
sha@1.0.9
shell-server@0.5.0
socket-stream-client@0.5.1
spacebars-compiler@1.3.1
socket-stream-client@0.5.2
spacebars-compiler@1.3.2
standard-minifier-js@2.8.1
static-html@1.3.2
svelte:compiler@3.46.4
templating-tools@1.2.2
templating-tools@1.2.3
tmeasday:check-npm-versions@1.0.2
tmeasday:publish-counts@0.8.0
tracker@1.3.2
typescript@4.9.4
underscore@1.0.13
tracker@1.3.3
typescript@4.9.5
underscore@1.6.1
url@1.3.2
webapp@1.13.5
webapp@1.13.8
webapp-hashing@1.1.1
zodern:melte@1.7.0
zodern:melte-compiler@1.4.0
zodern:melte@1.7.2
zodern:melte-compiler@1.4.1
.releaserc.js
local_modules
\ No newline at end of file
# The tag here should match the Meteor version of your app, per .meteor/release
FROM hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.13.3
FROM hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.15
# Copy app package.json and package-lock.json into container
#COPY ./app/package*.json $APP_SOURCE_FOLDER/
......
# Changelog
## [1.11.0](https://gitlab.mim-libre.fr/alphabet/sondage/compare/release/1.10.0...release/1.11.0) (2024-06-17)
### Features
* **about:** add link in footer and some text ([526f034](https://gitlab.mim-libre.fr/alphabet/sondage/commit/526f03473ee9054deee27353862bf83a16fe4825))
* **about:** build first version of about page ([091d03b](https://gitlab.mim-libre.fr/alphabet/sondage/commit/091d03bddc38632f8814ace8dd1dd983125f5a93))
* **about:** last modification in about text ([bc8bc14](https://gitlab.mim-libre.fr/alphabet/sondage/commit/bc8bc14993c612acd4374071cd50e76ea4f7f6b7))
* **answer:** change layout for connection infos ([0e627cc](https://gitlab.mim-libre.fr/alphabet/sondage/commit/0e627cc3f31eff4020b7bf97a2bd8e8d9708b6f5))
* **answer:** change text fro infos completion ([5517b1d](https://gitlab.mim-libre.fr/alphabet/sondage/commit/5517b1d9384d39efa427a93b89ea776d594e2482))
* **mail:** add ical event in owner mail when user validate a slot ([a9480ec](https://gitlab.mim-libre.fr/alphabet/sondage/commit/a9480ecbff6226f6e10f5498be2a5bcf6fb7aad3))
* **meetings:** change ical description in email to poll owner ([9d5fb4a](https://gitlab.mim-libre.fr/alphabet/sondage/commit/9d5fb4a756419a79bb3324a871f0856ba02d04ae))
* **meteor:** update meteor and packages to version 2.15 ([569cb87](https://gitlab.mim-libre.fr/alphabet/sondage/commit/569cb873fddb0a160d5e78e207b272ed6d8ea31a))
* **meteor:** update meteor packages ([be1e289](https://gitlab.mim-libre.fr/alphabet/sondage/commit/be1e2892936cc3cd4f579a0ac5144020cebffe39))
* **meteor:** update meteor version ([d0b66c5](https://gitlab.mim-libre.fr/alphabet/sondage/commit/d0b66c586cd273d40ff6d2550fd6eb234d63fe72))
* **node:** update npm outdated packages ([d6f8393](https://gitlab.mim-libre.fr/alphabet/sondage/commit/d6f83930b57a0461428a1f6682f771d797b3b5c8))
* **npm:** update npm packages ([794f8a1](https://gitlab.mim-libre.fr/alphabet/sondage/commit/794f8a125ef9a31af1890791176161d37dc00109))
* **poll:** add management for public or group poll ([5b55444](https://gitlab.mim-libre.fr/alphabet/sondage/commit/5b554444e8725fab4ebadd41cf93fca072230d8b))
* **polls:** add poll expiration feature ([26845df](https://gitlab.mim-libre.fr/alphabet/sondage/commit/26845df38febb0a5055fd1dfc01111cc041ff774))
* **polls:** completed polls are still accessible to participants ([b98a856](https://gitlab.mim-libre.fr/alphabet/sondage/commit/b98a8561c98a962033494a801aec5fce8952347c))
* **timezone:** time zone management in polls ([36ed069](https://gitlab.mim-libre.fr/alphabet/sondage/commit/36ed0697c9b28260afd5c1ba155658cb83ebe7e4))
### Bug Fixes
* **about:** correct grammatical errors ([133c379](https://gitlab.mim-libre.fr/alphabet/sondage/commit/133c3798eef6eb59043d885908ffbcf0fcedf703))
* **answer:** fix connected answer ([cadce08](https://gitlab.mim-libre.fr/alphabet/sondage/commit/cadce08baa356436bbb31e8f254968b917c3ed71))
* **events:** always create an event when validating poll date ([acc806c](https://gitlab.mim-libre.fr/alphabet/sondage/commit/acc806c37bce3584e1e5448bc960299b8f00c8e0))
* **libraries:** update vulnerable libraries and adapt application ([caed9bb](https://gitlab.mim-libre.fr/alphabet/sondage/commit/caed9bb7b7cdf08bf3877e6b3e0f84886a3d6607))
* **meetings:** always send email to creator when answering to poll ([0b3e441](https://gitlab.mim-libre.fr/alphabet/sondage/commit/0b3e441a88e48a0141a97285d5672f88a363da3b))
* **poll:** fix link in group notification ([2b5356f](https://gitlab.mim-libre.fr/alphabet/sondage/commit/2b5356f5249d07c11c3d78eec557b70f43b6fce9))
* **polls:** conditionnal fields in polls creation validation step ([d92a78f](https://gitlab.mim-libre.fr/alphabet/sondage/commit/d92a78ff20dd4b89da29d7a7b1bb501f3068a3d6))
* **polls:** conditionnal imputs in polls creation first step ([fb0d66a](https://gitlab.mim-libre.fr/alphabet/sondage/commit/fb0d66ad9c555bf4f11f9769c468152af935bd1b))
* **polls:** purge associated pollanswers when deleting an expired poll ([1a7a8b3](https://gitlab.mim-libre.fr/alphabet/sondage/commit/1a7a8b30cd2bf63d62952374538dc124cd0b9c0e))
* **polls:** send email to all participants when validating final date ([663f874](https://gitlab.mim-libre.fr/alphabet/sondage/commit/663f874ed1579cd8b9e1d5462b0e66bbd31256aa))
* **tests:** add timeout on before each ([78e1484](https://gitlab.mim-libre.fr/alphabet/sondage/commit/78e148405e663e37e34b3c4f4d0d64875a366e97))
* **validation:** better poll validation form ([d67002d](https://gitlab.mim-libre.fr/alphabet/sondage/commit/d67002df997e001915dd1b5285bf3698138a7ab7))
* **validation:** make displying of groups conditional ([efea183](https://gitlab.mim-libre.fr/alphabet/sondage/commit/efea18386983523ccd0d0d13b2020aacce0e9465))
## [1.10.0](https://gitlab.mim-libre.fr/alphabet/sondage/compare/release/1.9.0...release/1.10.0) (2024-01-30)
......
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
import { Tracker } from 'meteor/tracker';
import { _ } from 'svelte-i18n';
const AppSettings = new Mongo.Collection('appsettings');
......@@ -69,7 +68,7 @@ AppSettings.schema = new SimpleSchema(
optional: true,
},
},
{ clean: { removeEmptyStrings: false }, tracker: Tracker },
{ clean: { removeEmptyStrings: false } },
);
AppSettings.publicFields = {
......
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
import { Tracker } from 'meteor/tracker';
export const EventsAgenda = new Mongo.Collection('eventsAgenda');
......@@ -48,88 +47,85 @@ const settingsParticipant = new SimpleSchema({
},
});
EventsAgenda.schema = new SimpleSchema(
{
eventType: {
type: String,
optional: true,
defaultValue: 'rdv',
},
title: {
type: String,
optional: false,
},
location: {
type: String,
optional: true,
},
description: {
type: String,
optional: true,
},
start: {
type: Date,
optional: false,
},
end: {
type: Date,
optional: false,
},
allDay: {
type: Boolean,
optional: false,
},
recurrent: {
type: Boolean,
defaultValue: false,
},
groups: {
type: Array,
optional: true,
},
'groups.$': {
type: settingsGroup,
optional: true,
},
participants: {
type: Array,
optional: true,
},
'participants.$': {
type: settingsParticipant,
optional: true,
},
guests: {
type: Array,
optional: true,
},
'guests.$': {
type: String,
optional: true,
},
userId: {
type: String,
optional: false,
},
createdAt: {
type: Date,
optional: true,
autoValue() {
if (this.isInsert) {
return new Date();
}
return this.value;
},
},
updatedAt: {
type: Date,
autoValue() {
EventsAgenda.schema = new SimpleSchema({
eventType: {
type: String,
optional: true,
defaultValue: 'rdv',
},
title: {
type: String,
optional: false,
},
location: {
type: String,
optional: true,
},
description: {
type: String,
optional: true,
},
start: {
type: Date,
optional: false,
},
end: {
type: Date,
optional: false,
},
allDay: {
type: Boolean,
optional: false,
},
recurrent: {
type: Boolean,
defaultValue: false,
},
groups: {
type: Array,
optional: true,
},
'groups.$': {
type: settingsGroup,
optional: true,
},
participants: {
type: Array,
optional: true,
},
'participants.$': {
type: settingsParticipant,
optional: true,
},
guests: {
type: Array,
optional: true,
},
'guests.$': {
type: String,
optional: true,
},
userId: {
type: String,
optional: false,
},
createdAt: {
type: Date,
optional: true,
autoValue() {
if (this.isInsert) {
return new Date();
},
}
return this.value;
},
},
{ tracker: Tracker },
);
updatedAt: {
type: Date,
autoValue() {
return new Date();
},
},
});
EventsAgenda.publicFields = {
title: 1,
......
......@@ -59,6 +59,14 @@ export function sendEmailToCreator(poll, answer, userId) {
const admin = Meteor.users.findOne(poll.userId);
const slots = !Array.isArray(answer.meetingSlot) ? [answer.meetingSlot] : answer.meetingSlot;
slots.forEach((slot) => {
const cal = ical({ domain: process.env.ROOT_URL, name: 'sondage iCal' });
cal.createEvent({
start: moment(slot),
end: moment(slot).add(DURATIONS_TIME[poll.duration], 'minute'),
summary: poll.title,
description: `Rendez vous avec ${answer.name}`,
url: new URL(ROUTES.ANSWER_POLL_RM(poll._id), process.env.ROOT_URL).href,
});
const html = template({
title: poll.title,
sender: answerer,
......@@ -71,6 +79,7 @@ export function sendEmailToCreator(poll, answer, userId) {
to: admin.emails[0].address,
from: Meteor.settings.smtp.fromEmail,
subject: `Rendez-vous - nouveau créneau sélectionné pour le rendez-vous ${poll.title}`,
icalEvent: cal.toString(),
inReplyTo: Meteor.settings.smtp.toEmail,
html,
});
......@@ -180,36 +189,57 @@ export function deleteEventAgendaMeeting(poll, answer, userId) {
}
}
export function createEventAgenda(poll, date, userId) {
let answers = [];
export function createEventAgenda(poll, date, organizerId) {
let guestAnswers = [];
// find all guest participants (public poll)
if (poll.public) {
answers = PollsAnswers.find({ userId: null, pollId: poll._id }).fetch();
guestAnswers = PollsAnswers.find({ userId: null, pollId: poll._id }).fetch();
}
let groupParticipants = [];
let groups = [];
// find all laboite participants based on groups
if (poll.groups.length > 0) {
groups = Groups.find({ _id: { $in: poll.groups } }).fetch();
groupParticipants = groups
.map(({ _id, admins, animators, members }) =>
Meteor.users
.find({ _id: { $in: [...admins, ...animators, ...members] } })
.fetch()
.map((user) => ({
_id: user._id,
email: user.emails[0].address,
groupId: _id,
status: user._id === organizerId ? 2 : 1,
})),
)
.flat(1);
}
const groups = Groups.find({ _id: { $in: poll.groups } }).fetch();
const participants = groups
.map(({ _id, admins, animators, members }) =>
Meteor.users
.find({ _id: { $in: [...admins, ...animators, ...members] } })
.fetch()
.map((user) => ({
_id: user._id,
email: user.emails[0].address,
groupId: _id,
status: 1,
})),
)
.flat(1)
.filter((v, i, a) => a.findIndex((t) => t._id === v._id) === i); // remove duplicate participants based on _id prop
// find laboite participants based on registered answers
const connectedAnswers = PollsAnswers.find({ userId: { $ne: null }, pollId: poll._id }).fetch();
const connectedParticipants = Meteor.users
.find({ _id: { $in: connectedAnswers.map(({ userId }) => userId) } })
.fetch()
.map((user) => ({
_id: user._id,
email: user.emails[0].address,
status: user._id === organizerId ? 2 : 1,
}));
// global participants list
const allParticipants = [...groupParticipants, ...connectedParticipants].filter(
// remove duplicate participants based on _id prop
(v, i, a) => a.findIndex((t) => t._id === v._id) === i,
);
// add an event in agenda for organizer and connected participants
EventsAgenda.insert({
title: poll.title,
location: '',
start: moment(date).format(),
end: moment(date).add(DURATIONS_TIME[poll.duration], 'minute').format(),
allDay: poll.allDay,
participants,
guests: answers.map(({ email }) => email),
participants: allParticipants,
guests: guestAnswers.map(({ email }) => email),
description: poll.description,
groups: groups.map(({ _id, name }) => ({ _id, name })),
userId,
userId: organizerId,
});
}
......@@ -11,7 +11,7 @@ const sendnotif = ({ groups, title, pollId, content, internalLink }) => {
if (internalLink) {
const groupSlug = Groups.findOne(groups[0]).slug; // Take the first groupId to make the link
if (groupSlug) {
link = `${Meteor.settings.public.laboiteHost}/groups/${groupSlug}/events`;
link = `${Meteor.settings.public.laboiteHost}/groups/${groupSlug}/services/events`;
}
}
axios
......
......@@ -5,6 +5,7 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method';
import Polls from './polls';
import PollsAnswers from '../polls_answers/polls_answers';
import validateString from '../../utils/functions/strings';
import RegEx from '../../utils/regExp';
export const validatePoll = (data) => {
validateString(data.title);
......@@ -37,7 +38,7 @@ export const removePolls = new ValidatedMethod({
validate: new SimpleSchema({
pollId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
regEx: RegEx.Id,
},
}).validator({ clean: true }),
......@@ -62,7 +63,7 @@ export const toggleActivePoll = new ValidatedMethod({
validate: new SimpleSchema({
pollId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
regEx: RegEx.Id,
},
}).validator({ clean: true }),
......
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
import { Tracker } from 'meteor/tracker';
import { POLLS_TYPES } from '../../utils/enums';
import RegEx from '../../utils/regExp';
const Polls = new Mongo.Collection('polls');
......@@ -18,21 +18,6 @@ Polls.deny({
},
});
const SingleDateSchema = new SimpleSchema({
date: {
type: Date,
label: 'Date',
},
slots: {
type: Array,
label: 'Time Slots',
optional: true,
},
'slots.$': {
type: String,
},
});
const SingleMeetingSlotSchema = new SimpleSchema({
start: {
type: Date,
......@@ -48,7 +33,7 @@ Polls.schema = new SimpleSchema(
{
_id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
regEx: RegEx.Id,
optional: true,
},
title: {
......@@ -58,7 +43,7 @@ Polls.schema = new SimpleSchema(
},
userId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
regEx: RegEx.Id,
label: 'Owner',
},
description: {
......@@ -109,7 +94,7 @@ Polls.schema = new SimpleSchema(
},
'groups.$': {
type: String,
regEx: SimpleSchema.RegEx.Id,
regEx: RegEx.Id,
},
dates: {
type: Array,
......@@ -117,7 +102,7 @@ Polls.schema = new SimpleSchema(
defaultValue: [],
},
'dates.$': {
type: SingleDateSchema,
type: Date,
},
meetingSlots: {
type: Array,
......@@ -144,13 +129,18 @@ Polls.schema = new SimpleSchema(
return new Date();
},
},
expiresAt: {
type: Date,
label: 'Expiration date',
optional: true,
},
hideParticipantsList: {
type: Boolean,
label: 'Hide participants list',
defaultValue: false,
},
},
{ clean: { removeEmptyStrings: false }, tracker: Tracker },
{ clean: { removeEmptyStrings: false } },
);
Polls.publicFields = {
......
......@@ -3,19 +3,22 @@ import { DDPRateLimiter } from 'meteor/ddp-rate-limiter';
import SimpleSchema from 'simpl-schema';
import moment from 'moment';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { SyncedCron } from 'meteor/littledata:synced-cron';
import Polls from '../polls';
import Groups from '../../groups/groups';
import PollsAnswers from '../../polls_answers/polls_answers';
import { sendEmail, createEventAgenda } from '../../events/server/methods';
import { validatePoll } from '../methods';
import getExpirationDate from '../../../utils/functions/polls';
import RegEx from '../../../utils/regExp';
export const getSinglePoll = new ValidatedMethod({
name: 'polls.getSinglePoll',
validate: new SimpleSchema({
pollId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
regEx: RegEx.Id,
},
}).validator({ clean: true }),
......@@ -29,7 +32,7 @@ export const getSinglePollToAnswer = new ValidatedMethod({
validate: new SimpleSchema({
pollId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
regEx: RegEx.Id,
},
}).validator({ clean: true }),
......@@ -56,9 +59,6 @@ export const getSinglePollToAnswer = new ValidatedMethod({
if ((!poll.public && !this.userId) || (!isInAGroup && !poll.public && this.userId !== poll.userId)) {
throw new Meteor.Error('api.polls.methods.get.notPublic', 'api.errors.notApublicPoll');
}
if (!poll.active && this.userId !== poll.userId) {
throw new Meteor.Error('api.polls.methods.get.notActive', 'api.errors.pollNotActive');
}
return data;
},
});
......@@ -69,7 +69,7 @@ export const updatePoll = new ValidatedMethod({
data: Polls.schema.omit('createdAt', 'updatedAt'),
pollId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
regEx: RegEx.Id,
},
}).validator({ clean: true }),
......@@ -95,7 +95,7 @@ export const validatePollAnswer = new ValidatedMethod({
validate: new SimpleSchema({
pollId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
regEx: RegEx.Id,
},
date: Date,
}).validator({ clean: true }),
......@@ -109,8 +109,8 @@ export const validatePollAnswer = new ValidatedMethod({
throw new Meteor.Error('api.polls_answers.methods.validate.notAllowed', 'api.errors.notAllowed');
}
createEventAgenda(poll, date, this.userId);
if (poll.groups.length) {
createEventAgenda(poll, date, this.userId);
if (!Meteor.isTest) {
// eslint-disable-next-line global-require
const sendnotif = require('../../notifications/server/notifSender').default;
......@@ -124,25 +124,58 @@ export const validatePollAnswer = new ValidatedMethod({
}
}
const result = Polls.update({ _id: pollId }, { $set: { completed: true, choosenDate: date, active: false } });
// find answers of laboite users
let answers = PollsAnswers.find({ userId: { $ne: null }, pollId }).fetch();
if (poll.public) {
const answers = PollsAnswers.find({ userId: null, pollId }).fetch();
let emailErrors = false;
answers.forEach((answer) => {
try {
sendEmail(poll, {
...answer,
meetingSlot: date,
});
} catch (error) {
emailErrors = true;
}
});
if (emailErrors) throw new Meteor.Error('api.events.methods.sendEmail', 'api.errors.cannotSendEmail');
// for public polls, add non laboite users answers
const connectedAnswers = PollsAnswers.find({ userId: null, pollId }).fetch();
answers = [...answers, ...connectedAnswers];
}
// send email to all answerers
let emailErrors = false;
answers.forEach((answer) => {
try {
sendEmail(poll, {
...answer,
meetingSlot: date,
});
} catch (error) {
emailErrors = true;
}
});
if (emailErrors) throw new Meteor.Error('api.events.methods.sendEmail', 'api.errors.cannotSendEmail');
return result;
},
});
SyncedCron.add({
name: 'Delete expired polls',
schedule: function removeSchedule(parser) {
return parser.text(`at 3:00 am`);
},
job: function removeOldPolls() {
// update old polls without expiresAt field
Polls.find({ expiresAt: null })
.fetch()
.forEach((oldPoll) => {
// calculate default expirationDate (7 days after last poll/meeting date)
const expDate = getExpirationDate(oldPoll);
Polls.update({ _id: oldPoll._id }, { $set: { expiresAt: expDate } });
});
// remove expired polls
const expiredPolls = Polls.find({
expiresAt: {
$lt: new Date(),
},
}).fetch();
expiredPolls.forEach((poll) => {
PollsAnswers.remove({ pollId: poll._id });
Polls.remove({ _id: poll._id });
});
return expiredPolls.length;
},
});
const methodsKeys = [
'polls.create',
'polls.remove',
......
......@@ -72,6 +72,7 @@ describe('polls', function () {
});
describe('polls.member', function () {
beforeEach(function () {
this.timeout(3000);
Polls.remove({});
Meteor.users.remove({});
// 1 pollOwner
......@@ -432,20 +433,6 @@ describe('polls', function () {
/api.polls.methods.get.notPublic/,
);
});
it('if poll is public not active and user is not pollOwner', function () {
const pollPublicNotActive = Factory.create('poll', {
userId: ownerPollUser._id,
active: false,
public: true,
});
assert.throws(
() => {
getSinglePollToAnswer._execute({ userId: anotherUser._id }, { pollId: pollPublicNotActive._id });
},
Meteor.Error,
/api.polls.methods.get.notActive/,
);
});
});
describe('should return data', function () {
it('without answer if user have not account ', function () {
......
......@@ -29,7 +29,6 @@ Meteor.publish('polls.member', function pollMember({ page, limit }) {
{ fields: { _id: 1 } },
);
const query = {
active: true,
type: POLLS_TYPES.POLL,
$or: [
{ userId: this.userId },
......@@ -59,7 +58,6 @@ Meteor.publish('polls.meetings.member', function pollMeetingMember({ page, limit
{ fields: { _id: 1 } },
);
const query = {
active: true,
type: POLLS_TYPES.MEETING,
$or: [
{ userId: this.userId },
......
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
import { Tracker } from 'meteor/tracker';
import RegEx from '../../utils/regExp';
const PollsAnswers = new Mongo.Collection('polls_answers');
......@@ -22,14 +22,14 @@ const SingleDateSchema = new SimpleSchema({
type: Date,
label: 'Date',
},
slots: {
type: Array,
label: 'Time Slots',
optional: true,
},
'slots.$': {
type: String,
},
// slots: {
// type: Array,
// label: 'Time Slots',
// optional: true,
// },
// 'slots.$': {
// type: String,
// },
present: {
type: Boolean,
label: 'Full day presence',
......@@ -41,18 +41,18 @@ PollsAnswers.schema = new SimpleSchema(
{
_id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
regEx: RegEx.Id,
optional: true,
},
userId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
regEx: RegEx.Id,
label: 'Owner',
optional: true,
},
email: {
type: String,
regEx: SimpleSchema.RegEx.Email,
regEx: RegEx.Email,
label: 'Owner Email',
},
name: {
......@@ -62,7 +62,7 @@ PollsAnswers.schema = new SimpleSchema(
},
pollId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
regEx: RegEx.Id,
label: 'Poll ID',
},
meetingSlot: {
......@@ -105,7 +105,7 @@ PollsAnswers.schema = new SimpleSchema(
},
},
},
{ clean: { removeEmptyStrings: false }, tracker: Tracker },
{ clean: { removeEmptyStrings: false } },
);
PollsAnswers.publicFields = {
......
......@@ -18,7 +18,34 @@ import {
} from '../../events/server/methods';
import validateString from '../../../utils/functions/strings';
import slotsIncludes from '../../../utils/functions/answers';
import RegEx from '../../../utils/regExp';
function updateAnswers(data, poll, previousAnswer, userId) {
const result = PollsAnswers.update(
{ pollId: data.pollId, email: data.email },
{ $set: { ...data, userId, confirmed: false } },
{ upsert: true },
);
if (poll.type === POLLS_TYPES.MEETING) {
// email poll owner about new slots chosen by user
sendEmailToCreator(poll, data, userId);
if (previousAnswer && previousAnswer.confirmed) {
// if previous answer was confirmed by poll owner
// - delete previously created events from poll owner agenda
// - email poll owner about cancelled meeting slots
deleteEventAgendaMeeting(poll, previousAnswer, poll.userId);
const initialSlots = Array.isArray(previousAnswer.meetingSlot)
? previousAnswer.meetingSlot
: [previousAnswer.meetingSlot];
initialSlots.forEach((slot) => {
if (!slotsIncludes(data.meetingSlot, slot)) {
sendCancelEmailToCreator(poll, { ...data, meetingSlot: [slot] }, '');
}
});
}
}
return result;
}
export const createPollAnswers = new ValidatedMethod({
name: 'polls_answers.create',
validate: new SimpleSchema({
......@@ -47,11 +74,11 @@ export const createPollAnswers = new ValidatedMethod({
}
validateString(data.email);
validateString(data.name);
if (data.choices) {
data.choices.forEach((choice) => {
if (choice.slots) choice.slots.forEach((slot) => validateString(slot));
});
}
// if (data.choices) {
// data.choices.forEach((choice) => {
// if (choice.slots) choice.slots.forEach((slot) => validateString(slot));
// });
// }
if (!poll.public && this.userId) {
const isInAGroup = Groups.findOne(
{
......@@ -67,11 +94,8 @@ export const createPollAnswers = new ValidatedMethod({
},
);
if ((isInAGroup && poll.active) || (this.userId === poll.userId && poll.active)) {
return PollsAnswers.update(
{ pollId: data.pollId, email: data.email },
{ $set: { ...data, userId: this.userId, confirmed: false } },
{ upsert: true },
);
const result = updateAnswers(data, poll, previousAnswer, this.userId);
return result;
}
if (!poll.active) {
throw new Meteor.Error('api.polls_answers.methods.create.notActivePoll', 'api.errors.notAllowed');
......@@ -79,29 +103,7 @@ export const createPollAnswers = new ValidatedMethod({
throw new Meteor.Error('api.polls_answers.methods.create.notAllowed', 'api.errors.notAllowed');
}
} else if ((poll.public || this.userId) && poll.active) {
const result = PollsAnswers.update(
{ pollId: data.pollId, email: data.email },
{ $set: { ...data, userId: this.userId, confirmed: false } },
{ upsert: true },
);
if (poll.type === POLLS_TYPES.MEETING) {
// email poll owner about new slots chosen by user
sendEmailToCreator(poll, data, this.userId);
if (previousAnswer && previousAnswer.confirmed) {
// if previous answer was confirmed by poll owner
// - delete previously created events from poll owner agenda
// - email poll owner about cancelled meeting slots
deleteEventAgendaMeeting(poll, previousAnswer, poll.userId);
const initialSlots = Array.isArray(previousAnswer.meetingSlot)
? previousAnswer.meetingSlot
: [previousAnswer.meetingSlot];
initialSlots.forEach((slot) => {
if (!slotsIncludes(data.meetingSlot, slot)) {
sendCancelEmailToCreator(poll, { ...data, meetingSlot: [slot] }, '');
}
});
}
}
const result = updateAnswers(data, poll, previousAnswer, this.userId);
return result;
} else if (!poll.public && !this.userId) {
throw new Meteor.Error('api.polls_answers.methods.create.notPublic', 'api.errors.pollNotActive');
......@@ -167,7 +169,7 @@ export const editMeetingPollAnswer = new ValidatedMethod({
emailNotice: Boolean,
email: {
type: String,
regEx: SimpleSchema.RegEx.Email,
regEx: RegEx.Email,
},
name: String,
meetingSlot: Array,
......
......@@ -23,8 +23,16 @@ import './server-router';
// libraries
import moment from 'moment';
import { SyncedCron } from 'meteor/littledata:synced-cron';
moment.locale('fr');
const { url } = Meteor.settings.smtp;
process.env.MAIL_URL = url;
SyncedCron.config({
log: true,
// Name of collection to use for synchronisation and logging
collectionTTL: 172800,
});
SyncedCron.start();