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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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