Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "eid-wallet_iOS/eid-wallet_iOS.entitlements";
CODE_SIGN_IDENTITY = "iPhone Developer";
CURRENT_PROJECT_VERSION = 0.2.1.0;
CURRENT_PROJECT_VERSION = 0.2.1.1;
DEVELOPMENT_TEAM = M49C8XS835;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64;
Expand Down Expand Up @@ -436,7 +436,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "eid-wallet_iOS/eid-wallet_iOS.entitlements";
CODE_SIGN_IDENTITY = "iPhone Developer";
CURRENT_PROJECT_VERSION = 0.2.1.0;
CURRENT_PROJECT_VERSION = 0.2.1.1;
DEVELOPMENT_TEAM = M49C8XS835;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>0.2.1.0</string>
<string>0.2.1.1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
on:click={handleVersionTap}
disabled={isRetrying}
>
Version v0.2.1.0
Version v0.2.1.1
</button>

{#if retryMessage}
Expand Down
48 changes: 48 additions & 0 deletions platforms/eVoting/src/app/(app)/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) {
}
}, [timeRemaining, selectedPoll]);

// Re-fetch results when poll expires (for all poll types)
useEffect(() => {
if (selectedPoll && timeRemaining === "Voting has ended") {
// Re-fetch fresh results when deadline expires
fetchVoteData();
}
}, [timeRemaining, selectedPoll]);

// Check if voting is still allowed
const isVotingAllowed =
selectedPoll &&
Expand Down Expand Up @@ -356,6 +364,26 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) {
<BarChart3 className="mr-2 h-5 w-5" />
Final Results
</h3>

{/* Voting Turnout Information */}
{blindVoteResults?.totalEligibleVoters && (
<div className="mb-4 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<Users className="h-4 w-4 text-blue-600" />
<span className="text-sm font-medium text-blue-900">Voting Turnout</span>
</div>
<div className="text-right">
<div className="text-lg font-bold text-blue-900">
{blindVoteResults.turnout?.toFixed(1) || 0}%
</div>
<div className="text-xs text-blue-600">
{blindVoteResults.totalVotes || 0} of {blindVoteResults.totalEligibleVoters} eligible voters
</div>
</div>
</div>
</div>
)}
<div className="space-y-3">
{blindVoteResults?.optionResults && blindVoteResults.optionResults.length > 0 ? (
blindVoteResults.optionResults.map((result, index) => {
Expand Down Expand Up @@ -415,6 +443,26 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) {
<BarChart3 className="mr-2 h-5 w-5" />
Final Results
</h3>

{/* Voting Turnout Information */}
{resultsData?.totalEligibleVoters && (
<div className="mb-4 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<Users className="h-4 w-4 text-blue-600" />
<span className="text-sm font-medium text-blue-900">Voting Turnout</span>
</div>
<div className="text-right">
<div className="text-lg font-bold text-blue-900">
{resultsData.turnout?.toFixed(1) || 0}%
</div>
<div className="text-xs text-blue-600">
{resultsData.totalVotes || 0} of {resultsData.totalEligibleVoters} eligible voters
</div>
</div>
</div>
</div>
)}
<div className="space-y-3">
{resultsData?.results && resultsData.results.length > 0 ? (
resultsData.results.map((result, index) => {
Expand Down
15 changes: 14 additions & 1 deletion platforms/eVoting/src/app/(app)/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ const createPollSchema = z.object({
return true;
}, "Please select a valid group"),
options: z
.array(z.string().min(1, "Option cannot be empty"))
.array(z.string()
.min(1, "Option cannot be empty")
.refine((val) => !val.includes(','), "Commas are not allowed in option text")
)
.min(2, "At least 2 options required"),
deadline: z
.string()
Expand Down Expand Up @@ -139,6 +142,16 @@ export default function CreatePoll() {
};

const updateOption = (index: number, value: string) => {
// Prevent commas in option text
if (value.includes(',')) {
toast({
title: "Invalid Option",
description: "Commas are not allowed in option text as they can break the voting system.",
variant: "destructive",
});
return;
}

const newOptions = [...options];
newOptions[index] = value;
setOptions(newOptions);
Expand Down
2 changes: 2 additions & 0 deletions platforms/eVoting/src/lib/pollApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ export interface Group {
export interface PollResults {
poll: Poll;
totalVotes: number;
totalEligibleVoters?: number;
turnout?: number;
results: {
option: string;
votes: number;
Expand Down
9 changes: 9 additions & 0 deletions platforms/evoting-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ AppDataSource.initialize()
} catch (error) {
console.error("Failed to initialize SigningController:", error);
}

// Start cron jobs after database is ready
try {
const cronManager = new CronManagerService();
cronManager.startAllJobs();
console.log("Cron jobs started successfully");
} catch (error) {
console.error("Failed to start cron jobs:", error);
}
})
.catch((error: unknown) => {
console.error("Error during initialization:", error);
Expand Down
6 changes: 3 additions & 3 deletions platforms/evoting-api/src/services/CronManagerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ export class CronManagerService {
}

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

try {
Expand Down
96 changes: 39 additions & 57 deletions platforms/evoting-api/src/services/DeadlineCheckService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ export class DeadlineCheckService {

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

try {
console.log(`[${now.toISOString()}] 🔍 Checking for expired polls...`);

// Find all polls with deadlines that have passed and haven't had deadline messages sent yet
const expiredPolls = await this.pollRepository
.createQueryBuilder("poll")
Expand All @@ -32,7 +34,12 @@ export class DeadlineCheckService {
.andWhere("poll.deadline IS NOT NULL")
.getMany();

console.log(`Found ${expiredPolls.length} expired polls that need deadline messages`);
console.log(`[${now.toISOString()}] 📊 Found ${expiredPolls.length} expired polls that need deadline messages`);

if (expiredPolls.length === 0) {
console.log(`[${now.toISOString()}] ✅ No expired polls found, nothing to process`);
return;
}

for (const poll of expiredPolls) {
try {
Expand All @@ -56,25 +63,15 @@ export class DeadlineCheckService {
}

try {
// Get the final results for this poll using blind voting tally
let results;
try {
results = await this.voteService.tallyBlindVotes(poll.id);
} catch (error) {
console.log(`Poll ${poll.id} has no blind votes, using basic poll info`);
// If no blind votes, create basic results from poll options
results = {
totalVotes: 0,
optionResults: poll.options.map((option: string, index: number) => ({
optionId: `option_${index}`,
optionText: option,
voteCount: 0
}))
};
}
// Create a simple deadline message similar to poll creation message
const deadlineMessage = this.createSimpleDeadlineMessage(poll);

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

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

/**
* Create a comprehensive deadline message with voting results
* Create a simple deadline message similar to poll creation message
*/
private createDeadlineMessageWithResults(poll: Poll, results: any): string {
let resultsText = '';

if (results && results.optionResults) {
resultsText = '\n\n📊 **Final Results:**\n';
results.optionResults.forEach((result: any, index: number) => {
const emoji = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : '•';
resultsText += `${emoji} ${result.optionText}: ${result.voteCount} votes\n`;
});
} else if (results && results.results) {
// Fallback for old format
resultsText = '\n\n📊 **Final Results:**\n';
results.results.forEach((result: any, index: number) => {
const emoji = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : '•';
resultsText += `${emoji} ${result.option}: ${result.votes} votes (${result.percentage.toFixed(1)}%)\n`;
});
}

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!`;
private createSimpleDeadlineMessage(poll: Poll): string {
const voteUrl = `${process.env.PUBLIC_EVOTING_URL || 'http://localhost:3000'}/${poll.id}`;
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>`;
}

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

const totalExpired = await this.pollRepository.count({
where: {
deadline: { $lt: now } as any
}
});
// Use proper TypeORM syntax instead of MongoDB syntax
const totalExpired = await this.pollRepository
.createQueryBuilder("poll")
.where("poll.deadline < :now", { now })
.andWhere("poll.deadline IS NOT NULL")
.getCount();

const messagesSent = await this.pollRepository.count({
where: {
deadline: { $lt: now } as any,
deadlineMessageSent: true
}
});
const messagesSent = await this.pollRepository
.createQueryBuilder("poll")
.where("poll.deadline < :now", { now })
.andWhere("poll.deadline IS NOT NULL")
.andWhere("poll.deadlineMessageSent = :sent", { sent: true })
.getCount();

const pendingMessages = await this.pollRepository.count({
where: {
deadline: { $lt: now } as any,
deadlineMessageSent: false
}
});
const pendingMessages = await this.pollRepository
.createQueryBuilder("poll")
.where("poll.deadline < :now", { now })
.andWhere("poll.deadline IS NOT NULL")
.andWhere("poll.deadlineMessageSent = :sent", { sent: false })
.getCount();

return {
totalExpired,
Expand Down
Loading
Loading