@@ -3,12 +3,14 @@ import { AppDataSource } from "../database/data-source";
3
3
import { Vote , VoteDataByMode , NormalVoteData , PointVoteData , RankVoteData } from "../database/entities/Vote" ;
4
4
import { Poll } from "../database/entities/Poll" ;
5
5
import { User } from "../database/entities/User" ;
6
+ import { Group } from "../database/entities/Group" ;
6
7
import { VotingSystem , VoteData } from 'blindvote' ;
7
8
8
9
export class VoteService {
9
10
private voteRepository : Repository < Vote > ;
10
11
private pollRepository : Repository < Poll > ;
11
12
private userRepository : Repository < User > ;
13
+ private groupRepository : Repository < Group > ;
12
14
13
15
// Store VotingSystem instances per poll
14
16
private votingSystems = new Map < string , VotingSystem > ( ) ;
@@ -17,11 +19,50 @@ export class VoteService {
17
19
this . voteRepository = AppDataSource . getRepository ( Vote ) ;
18
20
this . pollRepository = AppDataSource . getRepository ( Poll ) ;
19
21
this . userRepository = AppDataSource . getRepository ( User ) ;
22
+ this . groupRepository = AppDataSource . getRepository ( Group ) ;
23
+ }
24
+
25
+ /**
26
+ * Check if a user can vote on a poll based on group membership
27
+ */
28
+ private async canUserVote ( pollId : string , userId : string ) : Promise < boolean > {
29
+ const poll = await this . pollRepository . findOne ( {
30
+ where : { id : pollId } ,
31
+ relations : [ 'creator' ]
32
+ } ) ;
33
+
34
+ if ( ! poll ) {
35
+ throw new Error ( 'Poll not found' ) ;
36
+ }
37
+
38
+ // If poll has no group, it's a public poll - anyone can vote
39
+ if ( ! poll . groupId ) {
40
+ return true ;
41
+ }
42
+
43
+ // If poll has a group, check if user is a member, admin, or participant
44
+ const group = await this . groupRepository
45
+ . createQueryBuilder ( 'group' )
46
+ . leftJoin ( 'group.members' , 'member' )
47
+ . leftJoin ( 'group.admins' , 'admin' )
48
+ . leftJoin ( 'group.participants' , 'participant' )
49
+ . where ( 'group.id = :groupId' , { groupId : poll . groupId } )
50
+ . andWhere ( '(member.id = :userId OR admin.id = :userId OR participant.id = :userId)' , { userId } )
51
+ . getOne ( ) ;
52
+
53
+ if ( ! group ) {
54
+ throw new Error ( 'User is not a member, admin, or participant of the group associated with this poll' ) ;
55
+ }
56
+
57
+ return true ;
20
58
}
21
59
22
60
// ===== NON-BLIND VOTING METHODS (for normal/point/rank modes) =====
23
61
24
62
async createVote ( pollId : string , userId : string , voteData : VoteData , mode : "normal" | "point" | "rank" = "normal" ) : Promise < Vote > {
63
+ // First check if user can vote on this poll
64
+ await this . canUserVote ( pollId , userId ) ;
65
+
25
66
const poll = await this . pollRepository . findOne ( {
26
67
where : { id : pollId }
27
68
} ) ;
@@ -313,6 +354,15 @@ export class VoteService {
313
354
314
355
async submitBlindVote ( pollId : string , voterId : string , voteData : any ) {
315
356
try {
357
+ // First check if user can vote on this poll (group membership check)
358
+ const user = await this . userRepository . findOne ( { where : { ename : voterId } } ) ;
359
+ if ( ! user ) {
360
+ throw new Error ( `User with ename ${ voterId } not found. User must exist before submitting blind vote.` ) ;
361
+ }
362
+
363
+ // Check group membership using the existing method
364
+ await this . canUserVote ( pollId , user . id ) ;
365
+
316
366
const votingSystem = this . getVotingSystemForPoll ( pollId ) ;
317
367
318
368
// Get poll to find options
@@ -374,12 +424,7 @@ export class VoteService {
374
424
revealed : false
375
425
} ;
376
426
377
- // For blind voting, look up the user by their ename (W3ID)
378
- const user = await this . userRepository . findOne ( { where : { ename : voterId } } ) ;
379
- if ( ! user ) {
380
- throw new Error ( `User with ename ${ voterId } not found. User must exist before submitting blind vote.` ) ;
381
- }
382
-
427
+ // User is already fetched from the group membership check above
383
428
const vote = this . voteRepository . create ( {
384
429
poll : { id : pollId } ,
385
430
user : { id : user . id } ,
@@ -515,6 +560,15 @@ export class VoteService {
515
560
516
561
async registerBlindVoteVoter ( pollId : string , voterId : string ) {
517
562
try {
563
+ // First check if user can vote on this poll (group membership check)
564
+ const user = await this . userRepository . findOne ( { where : { ename : voterId } } ) ;
565
+ if ( ! user ) {
566
+ throw new Error ( `User with ename ${ voterId } not found. User must exist before registering for blind voting.` ) ;
567
+ }
568
+
569
+ // Check group membership using the existing method
570
+ await this . canUserVote ( pollId , user . id ) ;
571
+
518
572
const votingSystem = this . getVotingSystemForPoll ( pollId ) ;
519
573
520
574
// Get poll details
@@ -561,6 +615,15 @@ export class VoteService {
561
615
// Generate vote data for a voter (used by eID wallet)
562
616
async generateVoteData ( pollId : string , voterId : string , chosenOptionId : string ) {
563
617
try {
618
+ // First check if user can vote on this poll (group membership check)
619
+ const user = await this . userRepository . findOne ( { where : { ename : voterId } } ) ;
620
+ if ( ! user ) {
621
+ throw new Error ( `User with ename ${ voterId } not found. User must exist before generating vote data.` ) ;
622
+ }
623
+
624
+ // Check group membership using the existing method
625
+ await this . canUserVote ( pollId , user . id ) ;
626
+
564
627
const votingSystem = this . getVotingSystemForPoll ( pollId ) ;
565
628
566
629
// Get poll details
0 commit comments