Skip to content
Commits on Source (44)
.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/
......
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,
});
}
......@@ -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();
......@@ -24,6 +24,7 @@
import Maintenance from './routes/Maintenance.svelte';
import UserWarning from './components/common/UserWarning.svelte';
import End from './routes/End.svelte';
import About from './routes/About.svelte';
router.subscribe((_) => window.scrollTo(0, 0));
......@@ -61,6 +62,9 @@
<Route path={ROUTES.END}>
<End />
</Route>
<Route path={ROUTES.ABOUT}>
<About />
</Route>
{#if $currentUser}
<Route path={ROUTES.POLLS} let:meta>
<Polls {meta} />
......
......@@ -4,32 +4,31 @@
import { DURATIONS_TIME } from '../../../utils/enums';
export let date;
export let slot = '';
export let duration = '';
export let allDay = false;
export let altcolor = false;
const FORMAT_KEY_DATE = 'HH:mm';
let endTime;
if (slot !== '') {
$: if (!allDay) {
endTime = moment(date)
.hour(slot.split(':')[0])
.minute(slot.split(':')[1])
.add(DURATIONS_TIME[duration], 'minutes')
.format(FORMAT_KEY_DATE);
}
$: alt = altcolor ? "-alt" : "";
</script>
<div class="day">
{moment(date).format($_('components.Time.dateDayPollFormat'))}
</div>
<div class="date">
<div class={`date${alt}`}>
{moment(date).format($_('components.Time.dateDatePollFormat'))}
</div>
<div class="month">
{moment(date).format($_('components.Time.dateMonthPollFormat'))}
</div>
{#if slot}
<div class="slot">
{slot} - {endTime}
{#if !allDay}
<div class={`slot${alt}`}>
{moment(date).format(FORMAT_KEY_DATE)} - {endTime}
</div>
{/if}
......@@ -47,4 +46,13 @@
font-size: 16px;
}
.date-alt {
color: var(--secondary);
font-size: 20px;
}
.slot-alt {
color: var(--secondary);
font-size: 16px;
}
</style>