Skip to content

Commit

Permalink
Merge pull request #1390 from UrsMetz/contact-organizers-form
Browse files Browse the repository at this point in the history
Contact organizers form
  • Loading branch information
leider committed Jul 8, 2019
2 parents 487d725 + 5d21134 commit 8315e48
Show file tree
Hide file tree
Showing 18 changed files with 532 additions and 55 deletions.
4 changes: 4 additions & 0 deletions INSTALL.md
Expand Up @@ -36,6 +36,10 @@ On Windows there are currently issues when running the tests:
mongod
```

#### Useful commands
1. `mongo swk` to connect to the database
1. `db.memberstore.find().pretty()` to prettry print the content of `memberstore`
1. `db.groupstore.update( { _id : ObjectId("5bf9d641b68a0cb25fa4515c") }, { $unset: {contactTheOrganizers: ""} } );` to remove property `contactTheOrganizers` from the group with id `ObjectId("5bf9d641b68a0cb25fa4515c")`

### ImageMagick
brew install imagemagick
Expand Down
1 change: 1 addition & 0 deletions config/beans.json
Expand Up @@ -65,6 +65,7 @@
"activityresult": {"module": "./softwerkskammer/lib/activityresults/activityresult"},
"gitDiff": {"module": "./softwerkskammer/lib/wiki/gitDiff"},
"group": {"module": "./softwerkskammer/lib/groups/group"},
"groupMapper": {"module": "./softwerkskammer/lib/groups/groupMapper"},
"member": {"module": "./softwerkskammer/lib/members/member"},
"message": {"module": "./softwerkskammer/lib/mailsender/message"},
"renderer": {"module": "./softwerkskammer/lib/commons/renderer"},
Expand Down
8 changes: 7 additions & 1 deletion locales/translation-de.json
Expand Up @@ -260,6 +260,7 @@
"groups": {
"contact": "Ansprechpartner",
"contacts": "Ansprechpartner",
"mail_the_contact_persons": "E-Mail an die Ansprechpartner",
"create": "Gruppe anlegen",
"edit": "Gruppe \"{{groupId}}\" bearbeiten",
"email_prefix": "Präfix für E-Mail-Subjects",
Expand All @@ -268,6 +269,7 @@
"has": "Diese Gruppe hat",
"join": "Gruppe beitreten",
"leave": "Gruppe verlassen",
"mailinglist": "Gruppen-Mailingliste",
"map_label": "Label in Karte",
"meetup_clone_title": "Kopiert existierende Events von Meetup.",
"meetupURL_label": "Meetup-URL (falls für diese Gruppe erforderlich)",
Expand All @@ -285,15 +287,18 @@
"name": "Die Adresse, unter der die Gruppe \"@softwerkskammer.org\" erreichbar ist.",
"title": "Anzeigetext innerhalb der Site, ist auch Realname für E-Mails",
"x_coord": "Waagerechte Position auf der Karte",
"y_coord": "Senkrechte Position auf der Karte"
"y_coord": "Senkrechte Position auf der Karte",
"mail_the_contact_persons": "Auf der Gruppenseite wird ein E-Mail-Knopf angezeigt, über den die Ansprechpartnern angeschrieben werden können."
},
"type": "Gruppenart",
"x_coord": "X-Koord.",
"y_coord": "Y-Koord."
},
"mailsender": {
"contact_the_organizers_disabled": "Das Feature \"E-Mail an die Ansprechpartner\" ist deaktiviert.",
"contents": "Inhalt",
"copy_to_self": "Eine Kopie an mich senden",
"group_has_no_organizers": "Die Gruppe hat keine Ansprechpartner.",
"invitation": "Einladung",
"notes-resign": "Was möchtest Du uns mitteilen (Anregungen, Kritik, Lob)",
"notification": "Nachricht",
Expand All @@ -309,6 +314,7 @@
"send": "Senden",
"subject": "Betreff",
"to_member": "An Mitglied",
"to_contact_persons": "An Ansprechpartner der Gruppe",
"to_participants": "nur an die Teilnehmer der Aktivität (nicht an Gruppen)",
"why-resign": "Warum willst Du austreten",
"write": "Nachricht schreiben"
Expand Down
8 changes: 7 additions & 1 deletion locales/translation-en.json
Expand Up @@ -269,6 +269,7 @@
"groups": {
"contact": "Contact person",
"contacts": "Contact persons",
"mail_the_contact_persons": "E-mail to the contact persons",
"create": "Create group",
"edit": "Edit group \"{{groupId}}\"",
"email_prefix": "Prefix for e-mail-subjects",
Expand All @@ -277,6 +278,7 @@
"has": "This group has",
"join": "Join Group",
"leave": "Leave Group",
"mailinglist": "Group mailinglist",
"map_label": "Label in map",
"more": "read more…",
"new": "New Group",
Expand All @@ -291,15 +293,18 @@
"name": "The e-mail address \"@softwerkskammer.org\" where the group can be reached.",
"title": "Name of the group used in the website, also realname for e-mails sent by the group",
"x_coord": "Horizontal position on the map",
"y_coord": "Vertical position on the map"
"y_coord": "Vertical position on the map",
"mail_the_contact_persons": "An e-mail button is displayed on the group page, which can be used to write to the contact persons."
},
"type": "Group type",
"x_coord": "X-coord.",
"y_coord": "Y-coord."
},
"mailsender": {
"contact_the_organizers_disabled": "The feature \"E-mail to the contact persons\" is disabled.",
"contents": "Contents",
"copy_to_self": "Send a copy to myself",
"group_has_no_organizers": "Group has no organizers.",
"invitation": "Invitation",
"notes-resign": "Please let us know what you think about this site",
"notification": "Message",
Expand All @@ -315,6 +320,7 @@
"send": "Send",
"subject": "Subject",
"to_member": "To member",
"to_contact_persons": "To contact persons of group",
"to_participants": "only to the activity's participants (not to groups)",
"why-resign": "Why do you want to resign",
"write": "Compose Message"
Expand Down
13 changes: 13 additions & 0 deletions softwerkskammer/lib/groups/group.js
Expand Up @@ -20,6 +20,7 @@ class Group {
this.mapX = object.mapX;
this.mapY = object.mapY;
this.shortName = object.shortName;
this.contactTheOrganizers = !!object.contactTheOrganizers;
} else {
this.color = '#FF00FF';
}
Expand All @@ -39,6 +40,10 @@ class Group {
return members.map(member => {return {member, checked: this.isOrganizer(member.id())}; });
}

membersThatAreOrganizers(members) {
return members.filter(member => this.isOrganizer(member.id()));
}

mapYrelative() {
return 100 * this.mapY / 441;
}
Expand All @@ -64,6 +69,14 @@ class Group {
}
}

canTheOrganizersBeContacted() {
return !!this.contactTheOrganizers && this.hasOrganizers();
}

hasOrganizers() {
return !R.isEmpty(this.organizers);
}

// Helper functions (static) -> look for a better place to implement
static regionalsFrom(groups) {
return groups.filter(group => group.type === regionalgruppe);
Expand Down
7 changes: 6 additions & 1 deletion softwerkskammer/lib/groups/views/get.pug
Expand Up @@ -29,8 +29,13 @@ block content
a.btn.btn-light(href='/groups/edit/' + group.id, title=t('general.edit')): i.fas.fa-edit
h1 #{group.longName} #{' '}
small #{group.type}
if (accessrights.canContactTheOrganizers(group))
p
a.btn.btn-success(href='/mailsender/contactGroupContactPersons/' + group.id)
i.far.fa-envelope
|  #{t('groups.mail_the_contact_persons')}
p
strong #{t('general.address')}: #{' '}
strong #{t('groups.mailinglist')}: #{' '}
a(href='mailto:' + group.id + '@softwerkskammer.org')
| #{group.id}@softwerkskammer.org
.row
Expand Down
1 change: 1 addition & 0 deletions softwerkskammer/lib/groups/views/groups-forms.pug
Expand Up @@ -45,6 +45,7 @@ mixin groupform(group, allTypes, organizersChecked)
+text('mapY', t('groups.y_coord'), group.mapY, t('groups.tooltip.y_coord'))
+text('shortName', t('groups.map_label'), group.shortName, t('groups.tooltip.map_label'))
+text('meetupURL', t('groups.meetupURL_label'), group.meetupURL, t('groups.tooltip.meetupURL_label'))
+checkboxWithDescription('contactTheOrganizers', t('groups.mail_the_contact_persons'), group.contactTheOrganizers, t('groups.tooltip.mail_the_contact_persons'))
.row
.col-md-12
if group.id
Expand Down
13 changes: 11 additions & 2 deletions softwerkskammer/lib/groupsAndMembers/groupsAndMembersService.js
Expand Up @@ -37,7 +37,6 @@ function groupsWithExtraEmailAddresses(members, groupNamesWithEmails) {
return result;
}


module.exports = {
getMemberWithHisGroups: function getMemberWithHisGroups(nickname, callback) {
memberstore.getMember(nickname, (err, member) => {
Expand Down Expand Up @@ -100,6 +99,17 @@ module.exports = {
});
},

getOrganizersOfGroup: function getOrganizersOfGroup (groupId, callback) {
this.getGroupAndMembersForList(groupId, (error, groupIncludingMembers) => {
if (error) {return callback(error);}
if (!groupIncludingMembers) {
return callback(null, []);
}
const organizers = groupIncludingMembers.membersThatAreOrganizers(groupIncludingMembers.members);
callback(null, organizers);
});
},

getGroupAndMembersForList: function getGroupAndMembersForList(groupname, globalCallback) {
async.waterfall(
[
Expand Down Expand Up @@ -199,6 +209,5 @@ module.exports = {
});
});
}

};

16 changes: 15 additions & 1 deletion softwerkskammer/lib/mailsender/index.js
Expand Up @@ -37,11 +37,13 @@ function messageSubmitted(req, res, next) {
if (req.body.nickname) {
return mailsenderService.sendMailToMember(req.body.nickname, message, processResult);
}
if (req.body.groupNameForContact) {
return mailsenderService.sendMailToContactPersonsOfGroup(req.body.groupNameForContact, message, processResult);
}
statusmessage.errorMessage('message.title.email_problem', 'message.content.mailsender.error_no_recipient').putIntoSession(req);
res.redirect(req.body.successURL);
}


const app = misc.expressAppIn(__dirname);

app.get('/invitation/:activityUrl', (req, res, next) => {
Expand All @@ -65,6 +67,18 @@ app.get('/contactMember/:nickname', (req, res, next) => {
});
});

app.get('/contactGroupContactPersons/:groupname', (req, res) => {
const groupName = req.params.groupname;

res.render('compose', {
message: new Message(),
successURL: '/groups/' + groupName,
contactPersons: {
groupName: groupName
}
});
});

app.post('/send', messageSubmitted);

app.get('/resign/:nickname', (req, res) => {
Expand Down
25 changes: 25 additions & 0 deletions softwerkskammer/lib/mailsender/mailsenderService.js
@@ -1,6 +1,7 @@
const async = require('async');
const {DateTime} = require('luxon');
const conf = require('simple-configure');
const logger = require('winston').loggers.get('application');

const beans = conf.get('beans');
const groupsService = beans.get('groupsService');
Expand Down Expand Up @@ -162,6 +163,30 @@ module.exports = {
message.setTo(superusers);
sendMail(message, 'E-Mail', callback);
});
},

sendMailToContactPersonsOfGroup: function sendMailToContactPersonsOfGroup (groupId, message, callback) {
const type = '$t(mailsender.notification)';
groupsService.getGroups([groupId], (groupLoadErr, groups) => {
if (groupLoadErr) { return callback(groupLoadErr, mailtransport.statusmessageForError(type, groupLoadErr)); }
if (groups.length !== 1) {
logger.error(`${groups.length} Gruppen für Id ${groupId} gefunden. Erwarte genau eine Gruppe.`);
const error = new Error('Das senden der E-Mail ist fehlgeschlagen. Es liegt ein technisches Problem vor.');
return callback(error, mailtransport.statusmessageForError(type, error));
}
if (!groups[0].canTheOrganizersBeContacted()) {
return callback(null, mailtransport.statusmessageForError(type, '$t(mailsender.contact_the_organizers_disabled)'));
}
groupsAndMembersService.getOrganizersOfGroup(groupId, (err, organizers) => {
if (err) { return callback(err, mailtransport.statusmessageForError(type, err)); }
if (!organizers.length) {
return callback(null, mailtransport.statusmessageForError(type, '$t(mailsender.group_has_no_organizers)'));
}
message.setSubject(`[Anfrage an Ansprechpartner/Mail to organizers] ${message.subject}`);
message.setBccToMemberAddresses(organizers);
sendMail(message, type, callback);
});
});
}

};
2 changes: 1 addition & 1 deletion softwerkskammer/lib/mailsender/views/compose.pug
Expand Up @@ -9,4 +9,4 @@ block title
| #{t('mailsender.notification')}

block content
+mailform(message, regionalgroups, themegroups, massMailing)
+mailform(message, regionalgroups, themegroups, massMailing, contactPersons)
4 changes: 3 additions & 1 deletion softwerkskammer/lib/mailsender/views/mailsender-forms.pug
@@ -1,6 +1,6 @@
include ../../../views/formComponents

mixin mailform(message, regionalgroups, themegroups, massMailing)
mixin mailform(message, regionalgroups, themegroups, massMailing, contactPersons)
form#mailform(action='/mailsender/send', method='post')
+csrf
fieldset
Expand All @@ -15,6 +15,8 @@ mixin mailform(message, regionalgroups, themegroups, massMailing)
+groupCheckboxes('invitedGroups', regionalgroups, themegroups)
h4 #{t('mailsender.others')}
+checkbox('toParticipants', t('mailsender.to_participants'), false, 'toParticipants')
if (contactPersons)
+text('groupNameForContact', t('mailsender.to_contact_persons'), contactPersons.groupName, '', '', true)
if (message.receiver)
+text('nickname', t('mailsender.to_member'), message.receiver.nickname(), '', '', true)
+text('subject', t('mailsender.subject'), message.subject)
Expand Down
4 changes: 4 additions & 0 deletions softwerkskammer/lib/middleware/accessrights.js
Expand Up @@ -74,6 +74,10 @@ module.exports = function accessrights(req, res, next) {
return this.isRegistered();
},

canContactTheOrganizers: function canContactTheOrganizers(group) {
return this.canParticipateInGroup() && group.canTheOrganizersBeContacted();
},

canEditPhoto: function canEditPhoto(photo) {
return this.isSuperuser() || (photo && photo.uploadedBy && photo.uploadedBy() === this.memberId());
},
Expand Down
74 changes: 74 additions & 0 deletions softwerkskammer/test/groups/group_object_test.js
Expand Up @@ -32,6 +32,24 @@ describe('Group object', () => {
expect(group.organizers).to.contain('idB');
});

describe('contactTheOrganizers flag', function () {
it('no contactTheOrganizers property it was not checked', () => {
const body = {
id: 'any-group-id',
};

expect(new Group(body).contactTheOrganizers).to.be(false);
});

it('contactTheOrganizers property with value on it was checked', () => {
const body = {
id: 'any-group-id',
contactTheOrganizers: 'on'
};

expect(new Group(body).contactTheOrganizers).to.be(true);
});
});
});

});
Expand Down Expand Up @@ -132,6 +150,26 @@ describe('answers that a', () => {

});

describe('list of organizers', () => {
it('returns empty list when no members are in group', () => {
const group = new Group({id: 'groupA', organizers: []});
expect(group.membersThatAreOrganizers([])).to.be.empty();
});

it('returns empty list when no organizers are in goup', () => {
const group = new Group({id: 'groupA', organizers: []});
expect(group.membersThatAreOrganizers([new Member({id: 'member1'})])).to.be.empty();
});

it('returns list with only those members that are organizers', () => {
const group = new Group({id: 'groupA', organizers: ['organizer1', 'organizer2']});
const organizer1 = new Member({id: 'organizer1'});
const organizer2 = new Member({id: 'organizer2'});
const members = [organizer1, new Member({id: 'no-organizer'}), organizer2];
expect(group.membersThatAreOrganizers(members)).to.eql([organizer1, organizer2]);
});
});

describe('delivers the symmetric difference of organizers to', () => {

it('no other group (first arg is undefined)', () => {
Expand Down Expand Up @@ -192,3 +230,39 @@ describe('returns the meetup :urlname from the given meetup URL', () => {
});

});

describe('hasOrganizers', () => {
it('returns false when there are no organizers', () => {
const group = new Group({id: 'id'});

expect(group.hasOrganizers()).to.be.false();
});

it('returns true when there are organizers', () => {
const group = new Group({id: 'id', organizers: ['organizer1', 'organizer2']});

expect(group.hasOrganizers()).to.be.true();
});
});

describe('contact the organizers is an opt in feature (for spam protection purposes)', () => {
it('for new groups', () => {
const group = new Group();
expect(group.canTheOrganizersBeContacted()).to.be.false();
});

it('for existing groups', () => {
const group = new Group({id: 'flag-not-provided-in-state'});
expect(group.canTheOrganizersBeContacted()).to.be.false();
});

it('honour organizers choice when there are organizers', () => {
const group = new Group({contactTheOrganizers: true, organizers: ['organizer1', 'organizer2'], id: 'not-relevant'});
expect(group.canTheOrganizersBeContacted()).to.be.true();
});

it('returns false when flag is enabled but there are no organizers', () => {
const group = new Group({contactTheOrganizers: true, organizers: [], id: 'not-relevant'});
expect(group.canTheOrganizersBeContacted()).to.be.false();
});
});

0 comments on commit 8315e48

Please sign in to comment.