Skip to content

Commit 3abf469

Browse files
authored
Merge pull request #36 from demokratie-live/sprint#7/abstimmen
Abstimmen ermöglicht
2 parents 730f78e + 12a2b62 commit 3abf469

File tree

7 files changed

+188
-11
lines changed

7 files changed

+188
-11
lines changed

.eslintrc.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
module.exports = {
2-
extends: 'airbnb-base',
3-
};
4-
2+
extends: "airbnb-base",
3+
rules: { "newline-per-chained-call": [2] }
4+
};

src/graphql/resolvers/Activity.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/* eslint no-underscore-dangle: ["error", { "allow": ["_id"] }] */
2+
import { Types } from 'mongoose';
23

34
export default {
45
Query: {
@@ -21,7 +22,13 @@ export default {
2122
if (!user) {
2223
throw new Error('No auth');
2324
}
24-
const procedure = await ProcedureModel.findOne({ procedureId });
25+
let searchQuery;
26+
if (Types.ObjectId.isValid(procedureId)) {
27+
searchQuery = { _id: Types.ObjectId(procedureId) };
28+
} else {
29+
searchQuery = { procedureId };
30+
}
31+
const procedure = await ProcedureModel.findOne(searchQuery);
2532
if (!procedure) {
2633
throw new Error('Procedure not found');
2734
}

src/graphql/resolvers/Vote.js

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/* eslint no-underscore-dangle: ["error", { "allow": ["_id"] }] */
2+
import { Types } from 'mongoose';
3+
4+
import Activity from './Activity';
5+
6+
const statesVoting = ['Beschlussempfehlung liegt vor'];
7+
const statesCompleted = [
8+
'Zurückgezogen',
9+
'Erledigt durch Ablauf der Wahlperiode',
10+
'Einbringung abgelehnt',
11+
'Für erledigt erklärt',
12+
'Bundesrat hat zugestimmt',
13+
'Abgelehnt',
14+
'Verkündet',
15+
'Verabschiedet',
16+
'Zusammengeführt mit... (siehe Vorgangsablauf)',
17+
'Bundesrat hat Vermittlungsausschuss nicht angerufen',
18+
];
19+
20+
export default {
21+
Query: {
22+
votes: (parent, { procedure }, { VoteModel, user }) =>
23+
VoteModel.aggregate([
24+
{ $match: { procedure: Types.ObjectId(procedure) } },
25+
{ $addFields: { voted: { $in: [user ? user._id : false, '$users'] } } },
26+
{
27+
$group: {
28+
_id: '$procedure',
29+
yes: { $sum: '$voteResults.yes' },
30+
no: { $sum: '$voteResults.no' },
31+
abstination: { $sum: '$voteResults.abstination' },
32+
voted: { $first: '$voted' },
33+
},
34+
},
35+
{
36+
$project: {
37+
_id: 1,
38+
voted: 1,
39+
voteResults: {
40+
yes: '$yes',
41+
no: '$no',
42+
abstination: '$abstination',
43+
},
44+
},
45+
},
46+
]).then(result =>
47+
result[0] || { voted: false, voteResults: { yes: null, no: null, abstination: null } }),
48+
},
49+
50+
Mutation: {
51+
vote: async (
52+
parent,
53+
{ procedure: procedureId, selection },
54+
{
55+
VoteModel, ProcedureModel, ActivityModel, user,
56+
},
57+
) => {
58+
if (!user) {
59+
throw new Error('No Auth!');
60+
}
61+
// TODO check if procedure is votable
62+
const procedure = await ProcedureModel.findById(procedureId);
63+
let state;
64+
if (statesVoting.some(s => s === procedure.currentStatus)) {
65+
state = 'VOTING';
66+
} else if (statesCompleted.some(s => s === procedure.currentStatus)) {
67+
state = 'COMPLETED';
68+
}
69+
70+
let vote = await VoteModel.findOne({ procedure });
71+
if (!vote) {
72+
vote = await VoteModel.create({ procedure, state });
73+
}
74+
const hasVoted = vote.users.some(uId => uId.equals(user._id));
75+
if (!hasVoted) {
76+
const voteUpdate = { $push: { users: user } };
77+
switch (selection) {
78+
case 'YES':
79+
voteUpdate.$inc = { 'voteResults.yes': 1 };
80+
break;
81+
case 'NO':
82+
voteUpdate.$inc = { 'voteResults.no': 1 };
83+
break;
84+
case 'ABSTINATION':
85+
voteUpdate.$inc = { 'voteResults.abstination': 1 };
86+
break;
87+
88+
default:
89+
break;
90+
}
91+
await VoteModel.findByIdAndUpdate(vote._id, { ...voteUpdate, state });
92+
}
93+
await Activity.Mutation.increaseActivity(
94+
parent,
95+
{ procedureId },
96+
{ ProcedureModel, ActivityModel, user },
97+
);
98+
return VoteModel.aggregate([
99+
{ $match: { procedure: procedure._id } },
100+
{ $addFields: { voted: { $in: [user._id, '$users'] } } },
101+
{
102+
$group: {
103+
_id: '$procedure',
104+
yes: { $sum: '$voteResults.yes' },
105+
no: { $sum: '$voteResults.no' },
106+
abstination: { $sum: '$voteResults.abstination' },
107+
voted: { $first: '$voted' },
108+
},
109+
},
110+
{
111+
$project: {
112+
_id: 1,
113+
voted: 1,
114+
voteResults: {
115+
yes: '$yes',
116+
no: '$no',
117+
abstination: '$abstination',
118+
},
119+
},
120+
},
121+
]).then(result =>
122+
result[0] || { voted: false, voteResults: { yes: null, no: null, abstination: null } });
123+
},
124+
},
125+
};

src/graphql/schemas/Procedure.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,6 @@ enum ProcedureType {
55
HOT
66
}
77
8-
type VoteResult {
9-
yes: Int
10-
no: Int
11-
abstination: Int
12-
notVote: Int
13-
}
14-
158
type Procedure {
169
_id: ID!
1710
title: String!

src/graphql/schemas/Vote.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export default `
2+
3+
enum VoteSelection {
4+
YES
5+
NO
6+
ABSTINATION
7+
}
8+
9+
type VoteResult {
10+
yes: Int
11+
no: Int
12+
abstination: Int
13+
notVote: Int
14+
}
15+
16+
type Vote {
17+
_id: ID!
18+
voted: Boolean
19+
voteResults: VoteResult
20+
}
21+
22+
type Mutation {
23+
vote(procedure: ID!, selection: VoteSelection!): Vote
24+
}
25+
26+
type Query {
27+
votes(procedure: ID!): Vote
28+
}
29+
`;

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import auth from './express/auth';
2121
import ProcedureModel from './models/Procedure';
2222
import UserModel from './models/User';
2323
import ActivityModel from './models/Activity';
24+
import VoteModel from './models/Vote';
2425

2526
const app = express();
2627

@@ -60,6 +61,7 @@ app.use(constants.GRAPHQL_PATH, (req, res, next) => {
6061
ProcedureModel,
6162
UserModel,
6263
ActivityModel,
64+
VoteModel,
6365
},
6466
tracing: true,
6567
cacheControl: true,

src/models/Vote.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/* eslint no-underscore-dangle: ["error", { "allow": ["_id"] }] */
2+
import mongoose, { Schema } from 'mongoose';
3+
4+
const VoteSchema = new Schema({
5+
procedure: {
6+
type: Schema.Types.ObjectId,
7+
ref: 'Procedure',
8+
required: true,
9+
},
10+
state: { type: String, enum: ['VOTING', 'COMPLETED'], required: true },
11+
users: [{ type: Schema.Types.ObjectId, ref: 'User' }],
12+
voteResults: {
13+
yes: { type: Number, default: 0 },
14+
no: { type: Number, default: 0 },
15+
abstination: { type: Number, default: 0 },
16+
},
17+
});
18+
19+
VoteSchema.index({ procedure: 1, state: 1 }, { unique: true });
20+
21+
export default mongoose.model('Vote', VoteSchema);

0 commit comments

Comments
 (0)