Skip to content

Commit e4cc57a

Browse files
committed
Read raffle candidate from dedicated channel (exclude orgs)
1 parent 193b666 commit e4cc57a

File tree

8 files changed

+110
-36
lines changed

8 files changed

+110
-36
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
## Environment
44

5-
- `SLACK_TOKEN` envvar: Slack token including at least user read and email read permissions
5+
- `SLACK_TOKEN` envvar: Slack token including at least `groups:read`, `users:read` and `users:read.email` permissions.
66
- `PORT` (optional, default: `3000`) envvar: HTTP port for the server to listen to
77
- `PRIZE_FILE` (optional, default: `./samples/prizes.json`) envvar: static JSON file for prize data
8+
- `ATTENDEE_CSV_FILE` envvar: static CSV file for alf.io attendee export
9+
10+
Note that the corresponding bot should be invited to private channels before the token can be used to retrieve those.
811

912
## Getting started
1013

index.js

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,43 @@
11
const {WebClient} = require('@slack/web-api');
22
const {Raffle} = require('./lib/raffle');
3-
const RaffleCandidateRepository = require('./lib/raffle-candidate-repository');
43
const RafflePrizeRepository = require('./lib/raffle-prize-repository');
54
const express = require('express');
65
const path = require('path');
76
const fs = require('fs');
7+
const SlackChannelRepository = require("./lib/slack-channel-repository");
8+
const SlackUserRepository = require("./lib/slack-user-repository");
9+
const RaffleCandidateService = require("./lib/raffle-candidate-service");
810

9-
const token = process.env.SLACK_TOKEN;
10-
if (!token) {
11-
throw new Error('Missing envvar SLACK_TOKEN');
11+
const readEnvOrFail = (key) => {
12+
const result = process.env[key];
13+
if (!result) {
14+
throw new Error(`Missing envvar ${key}`);
15+
}
16+
return result;
1217
}
18+
19+
const checkEnvVarPath = (path, envVar) => {
20+
if (!fs.statSync(path).isFile()) {
21+
throw new Error(`${path} does not point to a regular file. Please review your ${envVar} envvar configuration`);
22+
}
23+
}
24+
25+
const token = readEnvOrFail('SLACK_TOKEN');
26+
const attendeeCsvPath = readEnvOrFail('ATTENDEE_CSV_FILE');
27+
checkEnvVarPath(attendeeCsvPath, 'ATTENDEE_CSV_FILE');
1328
const port = process.env.PORT || 3000;
1429
const prizeFilePath = process.env.PRIZE_FILE || `${__dirname}/samples/prizes.json`;
15-
if (!fs.statSync(prizeFilePath).isFile()) {
16-
throw new Error(`${prizeFilePath} does not point to a regular file. Please review your PRIZE_FILE envvar configuration`);
17-
}
30+
checkEnvVarPath(prizeFilePath, 'PRIZE_FILE');
1831
const app = express();
1932
app.use(express.json());
2033

21-
const candidateRepository = new RaffleCandidateRepository(new WebClient(token));
34+
const slackClient = new WebClient(token);
2235
const prizeRepository = new RafflePrizeRepository(prizeFilePath);
36+
const raffleCandidateService = new RaffleCandidateService(new SlackChannelRepository(slackClient), new SlackUserRepository(slackClient));
2337

2438
(async () => {
2539
const prizes = await prizeRepository.getPrizes();
26-
const initialCandidates = await candidateRepository.getUsers((user) =>
27-
!user.is_admin &&
28-
!user.is_bot &&
29-
user.id !== RaffleCandidateRepository.SLACKBOT_ID);
40+
const initialCandidates = await raffleCandidateService.findInitialCandidates();
3041
const raffle = new Raffle(initialCandidates, prizes);
3142

3243
app.get('/', (req, res) => {

lib/raffle-candidate-repository.js

Lines changed: 0 additions & 20 deletions
This file was deleted.

lib/raffle-candidate-service.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
module.exports = class RaffleCandidateService {
2+
3+
constructor(slackChannelRepository, slackUserRepository) {
4+
this._slackChannelRepository = slackChannelRepository;
5+
this._slackUserRepository = slackUserRepository;
6+
}
7+
8+
async findInitialCandidates() {
9+
const orgIds = await this._findOrgIds();
10+
const allCandidateIds = await this._findAllRaffleCandidateIds();
11+
const eligibleParticipantIds = allCandidateIds.filter(candidateId => !orgIds.includes(candidateId));
12+
return this._slackUserRepository.findAllByIds(eligibleParticipantIds)
13+
}
14+
15+
async _findOrgIds() {
16+
return this._slackChannelRepository.findOne('paris2021-org', true)
17+
.then(channel => channel.id)
18+
.then(channelId => this._slackUserRepository.findAllMemberIds(channelId))
19+
}
20+
21+
async _findAllRaffleCandidateIds() {
22+
return this._slackChannelRepository.findOne('paris2021-raffle', false)
23+
.then(channel => channel.id)
24+
.then(channelId => this._slackUserRepository.findAllMemberIds(channelId))
25+
}
26+
}

lib/raffle-prize-repository.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module.exports = class RafflePrizeFileRepository {
55

66
constructor(prizeFile) {
77
const rawPrizes = fs.readFileSync(prizeFile);
8-
this._prizes = JSON.parse(rawPrizes).map((rawPrize) => new RafflePrize(rawPrize.description));
8+
this._prizes = JSON.parse(rawPrizes.toString()).map((rawPrize) => new RafflePrize(rawPrize.description));
99
}
1010

1111
async getPrizes() {

lib/slack-channel-repository.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
module.exports = class SlackChannelRepository {
2+
3+
constructor(webClient) {
4+
this._webClient = webClient;
5+
this._cachedChannels = null;
6+
}
7+
8+
async findOne(channelName, isPrivate = false) {
9+
if (!this._cachedChannels || this._cachedChannels.private !== isPrivate) {
10+
this._cachedChannels = {
11+
private: isPrivate,
12+
result: (await this._listChannels(isPrivate)).channels
13+
};
14+
}
15+
return this._cachedChannels.result.find(channel => channel.name === channelName)
16+
}
17+
18+
_listChannels(isPrivate) {
19+
return this._webClient.conversations.list({types: this._channelType(isPrivate)});
20+
}
21+
22+
_channelType(isPrivate) {
23+
if (isPrivate) {
24+
return 'private_channel'
25+
}
26+
return 'public_channel';
27+
}
28+
}

lib/slack-user-repository.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const {RaffleCandidate} = require('./raffle');
2+
3+
module.exports = class SlackUserRepository {
4+
5+
constructor(webClient) {
6+
this._webClient = webClient;
7+
this._cachedUsers = null;
8+
}
9+
10+
async findAllMemberIds(channelId) {
11+
return await this._webClient.conversations.members({channel: channelId})
12+
.then(response => response.members)
13+
}
14+
15+
async findAllByIds(ids) {
16+
return (await this._findMembers())
17+
.filter(user => ids.includes(user.id))
18+
.map((user) => new RaffleCandidate(user.id, user.real_name, user.profile.email, user.profile.image_192));
19+
}
20+
21+
async _findMembers() {
22+
if (!this._cachedUsers) {
23+
this._cachedUsers = (await this._webClient.users.list()).members;
24+
}
25+
return this._cachedUsers;
26+
}
27+
}

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
"version": "0.0.1",
44
"description": "Raffle for https://hack-commit-pu.sh events",
55
"main": "index.js",
6-
"scripts": {
7-
},
6+
"scripts": {},
87
"author": "Florent Biville <[email protected]>",
98
"license": "Apache-2.0",
109
"dependencies": {

0 commit comments

Comments
 (0)