Skip to content

Commit 38688c1

Browse files
authored
Chore/evoting fixes (#336)
* fix: issues with eVoting * feat: voting message
1 parent 731ef80 commit 38688c1

File tree

13 files changed

+443
-124
lines changed

13 files changed

+443
-124
lines changed
15.7 MB
Binary file not shown.

infrastructure/eid-wallet/src-tauri/gen/apple/eid-wallet.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@
388388
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
389389
CODE_SIGN_ENTITLEMENTS = "eid-wallet_iOS/eid-wallet_iOS.entitlements";
390390
CODE_SIGN_IDENTITY = "iPhone Developer";
391-
CURRENT_PROJECT_VERSION = 0.2.1.0;
391+
CURRENT_PROJECT_VERSION = 0.2.1.1;
392392
DEVELOPMENT_TEAM = M49C8XS835;
393393
ENABLE_BITCODE = NO;
394394
"EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64;
@@ -436,7 +436,7 @@
436436
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
437437
CODE_SIGN_ENTITLEMENTS = "eid-wallet_iOS/eid-wallet_iOS.entitlements";
438438
CODE_SIGN_IDENTITY = "iPhone Developer";
439-
CURRENT_PROJECT_VERSION = 0.2.1.0;
439+
CURRENT_PROJECT_VERSION = 0.2.1.1;
440440
DEVELOPMENT_TEAM = M49C8XS835;
441441
ENABLE_BITCODE = NO;
442442
"EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64;

infrastructure/eid-wallet/src-tauri/gen/apple/eid-wallet_iOS/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
</dict>
2929
</array>
3030
<key>CFBundleVersion</key>
31-
<string>0.2.1.0</string>
31+
<string>0.2.1.1</string>
3232
<key>LSRequiresIPhoneOS</key>
3333
<true/>
3434
<key>NSAppTransportSecurity</key>

infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@
126126
on:click={handleVersionTap}
127127
disabled={isRetrying}
128128
>
129-
Version v0.2.1.0
129+
Version v0.2.1.1
130130
</button>
131131

132132
{#if retryMessage}

platforms/eVoting/src/app/(app)/[id]/page.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,14 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) {
132132
}
133133
}, [timeRemaining, selectedPoll]);
134134

135+
// Re-fetch results when poll expires (for all poll types)
136+
useEffect(() => {
137+
if (selectedPoll && timeRemaining === "Voting has ended") {
138+
// Re-fetch fresh results when deadline expires
139+
fetchVoteData();
140+
}
141+
}, [timeRemaining, selectedPoll]);
142+
135143
// Check if voting is still allowed
136144
const isVotingAllowed =
137145
selectedPoll &&
@@ -356,6 +364,26 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) {
356364
<BarChart3 className="mr-2 h-5 w-5" />
357365
Final Results
358366
</h3>
367+
368+
{/* Voting Turnout Information */}
369+
{blindVoteResults?.totalEligibleVoters && (
370+
<div className="mb-4 p-4 bg-blue-50 border border-blue-200 rounded-lg">
371+
<div className="flex items-center justify-between">
372+
<div className="flex items-center space-x-2">
373+
<Users className="h-4 w-4 text-blue-600" />
374+
<span className="text-sm font-medium text-blue-900">Voting Turnout</span>
375+
</div>
376+
<div className="text-right">
377+
<div className="text-lg font-bold text-blue-900">
378+
{blindVoteResults.turnout?.toFixed(1) || 0}%
379+
</div>
380+
<div className="text-xs text-blue-600">
381+
{blindVoteResults.totalVotes || 0} of {blindVoteResults.totalEligibleVoters} eligible voters
382+
</div>
383+
</div>
384+
</div>
385+
</div>
386+
)}
359387
<div className="space-y-3">
360388
{blindVoteResults?.optionResults && blindVoteResults.optionResults.length > 0 ? (
361389
blindVoteResults.optionResults.map((result, index) => {
@@ -415,6 +443,26 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) {
415443
<BarChart3 className="mr-2 h-5 w-5" />
416444
Final Results
417445
</h3>
446+
447+
{/* Voting Turnout Information */}
448+
{resultsData?.totalEligibleVoters && (
449+
<div className="mb-4 p-4 bg-blue-50 border border-blue-200 rounded-lg">
450+
<div className="flex items-center justify-between">
451+
<div className="flex items-center space-x-2">
452+
<Users className="h-4 w-4 text-blue-600" />
453+
<span className="text-sm font-medium text-blue-900">Voting Turnout</span>
454+
</div>
455+
<div className="text-right">
456+
<div className="text-lg font-bold text-blue-900">
457+
{resultsData.turnout?.toFixed(1) || 0}%
458+
</div>
459+
<div className="text-xs text-blue-600">
460+
{resultsData.totalVotes || 0} of {resultsData.totalEligibleVoters} eligible voters
461+
</div>
462+
</div>
463+
</div>
464+
</div>
465+
)}
418466
<div className="space-y-3">
419467
{resultsData?.results && resultsData.results.length > 0 ? (
420468
resultsData.results.map((result, index) => {

platforms/eVoting/src/app/(app)/create/page.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ const createPollSchema = z.object({
3434
return true;
3535
}, "Please select a valid group"),
3636
options: z
37-
.array(z.string().min(1, "Option cannot be empty"))
37+
.array(z.string()
38+
.min(1, "Option cannot be empty")
39+
.refine((val) => !val.includes(','), "Commas are not allowed in option text")
40+
)
3841
.min(2, "At least 2 options required"),
3942
deadline: z
4043
.string()
@@ -139,6 +142,16 @@ export default function CreatePoll() {
139142
};
140143

141144
const updateOption = (index: number, value: string) => {
145+
// Prevent commas in option text
146+
if (value.includes(',')) {
147+
toast({
148+
title: "Invalid Option",
149+
description: "Commas are not allowed in option text as they can break the voting system.",
150+
variant: "destructive",
151+
});
152+
return;
153+
}
154+
142155
const newOptions = [...options];
143156
newOptions[index] = value;
144157
setOptions(newOptions);

platforms/eVoting/src/lib/pollApi.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ export interface Group {
6161
export interface PollResults {
6262
poll: Poll;
6363
totalVotes: number;
64+
totalEligibleVoters?: number;
65+
turnout?: number;
6466
results: {
6567
option: string;
6668
votes: number;

platforms/evoting-api/src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ AppDataSource.initialize()
3333
} catch (error) {
3434
console.error("Failed to initialize SigningController:", error);
3535
}
36+
37+
// Start cron jobs after database is ready
38+
try {
39+
const cronManager = new CronManagerService();
40+
cronManager.startAllJobs();
41+
console.log("Cron jobs started successfully");
42+
} catch (error) {
43+
console.error("Failed to start cron jobs:", error);
44+
}
3645
})
3746
.catch((error: unknown) => {
3847
console.error("Error during initialization:", error);

platforms/evoting-api/src/services/CronManagerService.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ export class CronManagerService {
3636
}
3737

3838
/**
39-
* Start the deadline check cron job (runs every 10 minutes)
39+
* Start the deadline check cron job (runs every 5 minutes)
4040
*/
4141
private startDeadlineCheckJob(): void {
42-
// Schedule: every 10 minutes at 0, 10, 20, 30, 40, 50 seconds
43-
this.deadlineCheckJob = cron.schedule('*/10 * * * *', async () => {
42+
// Schedule: every 5 minutes at 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55
43+
this.deadlineCheckJob = cron.schedule('*/5 * * * *', async () => {
4444
console.log(`[${new Date().toISOString()}] Running deadline check cron job...`);
4545

4646
try {

platforms/evoting-api/src/services/DeadlineCheckService.ts

Lines changed: 39 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ export class DeadlineCheckService {
1717

1818
/**
1919
* Check for polls with deadlines that have passed and send system messages
20-
* This method is designed to be called by a cron job every 10 minutes
20+
* This method is designed to be called by a cron job every 5 minutes
2121
*/
2222
async checkExpiredPolls(): Promise<void> {
2323
const now = new Date();
2424

2525
try {
26+
console.log(`[${now.toISOString()}] 🔍 Checking for expired polls...`);
27+
2628
// Find all polls with deadlines that have passed and haven't had deadline messages sent yet
2729
const expiredPolls = await this.pollRepository
2830
.createQueryBuilder("poll")
@@ -32,7 +34,12 @@ export class DeadlineCheckService {
3234
.andWhere("poll.deadline IS NOT NULL")
3335
.getMany();
3436

35-
console.log(`Found ${expiredPolls.length} expired polls that need deadline messages`);
37+
console.log(`[${now.toISOString()}] 📊 Found ${expiredPolls.length} expired polls that need deadline messages`);
38+
39+
if (expiredPolls.length === 0) {
40+
console.log(`[${now.toISOString()}] ✅ No expired polls found, nothing to process`);
41+
return;
42+
}
3643

3744
for (const poll of expiredPolls) {
3845
try {
@@ -56,25 +63,15 @@ export class DeadlineCheckService {
5663
}
5764

5865
try {
59-
// Get the final results for this poll using blind voting tally
60-
let results;
61-
try {
62-
results = await this.voteService.tallyBlindVotes(poll.id);
63-
} catch (error) {
64-
console.log(`Poll ${poll.id} has no blind votes, using basic poll info`);
65-
// If no blind votes, create basic results from poll options
66-
results = {
67-
totalVotes: 0,
68-
optionResults: poll.options.map((option: string, index: number) => ({
69-
optionId: `option_${index}`,
70-
optionText: option,
71-
voteCount: 0
72-
}))
73-
};
74-
}
66+
// Create a simple deadline message similar to poll creation message
67+
const deadlineMessage = this.createSimpleDeadlineMessage(poll);
7568

76-
// Create a comprehensive deadline message with results
77-
const deadlineMessage = this.createDeadlineMessageWithResults(poll, results);
69+
// Log the exact message that's about to be sent
70+
console.log(`[${new Date().toISOString()}] 📤 About to send deadline message for poll "${poll.title}" (${poll.id}):`);
71+
console.log(`📝 Message content:`);
72+
console.log(`---`);
73+
console.log(deadlineMessage);
74+
console.log(`---`);
7875

7976
// Send the system message
8077
await this.messageService.createSystemMessage({
@@ -95,27 +92,11 @@ export class DeadlineCheckService {
9592
}
9693

9794
/**
98-
* Create a comprehensive deadline message with voting results
95+
* Create a simple deadline message similar to poll creation message
9996
*/
100-
private createDeadlineMessageWithResults(poll: Poll, results: any): string {
101-
let resultsText = '';
102-
103-
if (results && results.optionResults) {
104-
resultsText = '\n\n📊 **Final Results:**\n';
105-
results.optionResults.forEach((result: any, index: number) => {
106-
const emoji = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : '•';
107-
resultsText += `${emoji} ${result.optionText}: ${result.voteCount} votes\n`;
108-
});
109-
} else if (results && results.results) {
110-
// Fallback for old format
111-
resultsText = '\n\n📊 **Final Results:**\n';
112-
results.results.forEach((result: any, index: number) => {
113-
const emoji = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : '•';
114-
resultsText += `${emoji} ${result.option}: ${result.votes} votes (${result.percentage.toFixed(1)}%)\n`;
115-
});
116-
}
117-
118-
return `🗳️ **Vote Results Are In!**\n\n"${poll.title}"\n\nVote ID: ${poll.id}\n\nCreated by: ${poll.creator.name}${resultsText}\n\nThis vote has ended. The results are final!`;
97+
private createSimpleDeadlineMessage(poll: Poll): string {
98+
const voteUrl = `${process.env.PUBLIC_EVOTING_URL || 'http://localhost:3000'}/${poll.id}`;
99+
return `eVoting Platform: Vote results are in!\n\n"${poll.title}"\n\nVote ID: ${poll.id}\n\nCreated by: ${poll.creator.name}\n\n<a href="${voteUrl}" target="_blank">View results here</a>`;
119100
}
120101

121102
/**
@@ -128,25 +109,26 @@ export class DeadlineCheckService {
128109
}> {
129110
const now = new Date();
130111

131-
const totalExpired = await this.pollRepository.count({
132-
where: {
133-
deadline: { $lt: now } as any
134-
}
135-
});
112+
// Use proper TypeORM syntax instead of MongoDB syntax
113+
const totalExpired = await this.pollRepository
114+
.createQueryBuilder("poll")
115+
.where("poll.deadline < :now", { now })
116+
.andWhere("poll.deadline IS NOT NULL")
117+
.getCount();
136118

137-
const messagesSent = await this.pollRepository.count({
138-
where: {
139-
deadline: { $lt: now } as any,
140-
deadlineMessageSent: true
141-
}
142-
});
119+
const messagesSent = await this.pollRepository
120+
.createQueryBuilder("poll")
121+
.where("poll.deadline < :now", { now })
122+
.andWhere("poll.deadline IS NOT NULL")
123+
.andWhere("poll.deadlineMessageSent = :sent", { sent: true })
124+
.getCount();
143125

144-
const pendingMessages = await this.pollRepository.count({
145-
where: {
146-
deadline: { $lt: now } as any,
147-
deadlineMessageSent: false
148-
}
149-
});
126+
const pendingMessages = await this.pollRepository
127+
.createQueryBuilder("poll")
128+
.where("poll.deadline < :now", { now })
129+
.andWhere("poll.deadline IS NOT NULL")
130+
.andWhere("poll.deadlineMessageSent = :sent", { sent: false })
131+
.getCount();
150132

151133
return {
152134
totalExpired,

0 commit comments

Comments
 (0)