Create, configure, and manage elections through their complete lifecycle from draft to closed
Consensus provides comprehensive election management capabilities, allowing you to create elections, add candidates, and control the election lifecycle.
Every election in Consensus follows a three-stage lifecycle:
// From src/domain/enums/index.ts:8-12export enum ElectionStatus { DRAFT = "DRAFT", // Election being set up ACTIVE = "ACTIVE", // Currently accepting votes CLOSED = "CLOSED", // Voting ended, results available}
1
DRAFT
Election is being configured. You can add/remove candidates and modify settings.
2
ACTIVE
Election is live and accepting votes. No modifications allowed.
3
CLOSED
Voting has ended. Results are available and final.
Once an election moves out of DRAFT status, candidates cannot be added or removed. Plan your candidate list carefully.
// From src/services/ElectionService.ts:49-51if (dto.startDate >= dto.endDate) { throw new Error("End date must be after start date");}
Start date not in the past
// From src/services/ElectionService.ts:54-61const today = new Date();today.setHours(0, 0, 0, 0);const startDay = new Date(dto.startDate);startDay.setHours(0, 0, 0, 0);if (startDay < today) { throw new Error("Start date cannot be in the past");}
The system compares dates only (not times), so you can create an election that starts “today” at any time.
Candidates can only be removed during DRAFT status:
// From src/services/ElectionService.ts:101-121removeCandidate(electionID: string, candidateID: string): void { const election = this.electionRepository.findById(electionID); if (!election) { throw new Error("Election not found"); } if (election.status !== ElectionStatus.DRAFT) { throw new Error("Cannot remove candidates from non-draft elections"); } const candidate = this.candidateRepository.findById(candidateID); if (!candidate) { throw new Error("Candidate not found"); } if (candidate.electionID !== electionID) { throw new Error("Candidate does not belong to this election"); } this.candidateRepository.delete(candidateID);}
Elections must have at least 2 candidates before activation:
// From src/services/ElectionService.ts:168-171const candidates = this.candidateRepository.findByElectionId(electionID);if (candidates.length < 2) { throw new Error("Election must have at least 2 candidates");}
// From src/services/ElectionService.ts:161-179activateElection(electionID: string): void { const election = this.electionRepository.findById(electionID); if (!election) { throw new Error("Election not found"); } // Validate election has candidates const candidates = this.candidateRepository.findByElectionId(electionID); if (candidates.length < 2) { throw new Error("Election must have at least 2 candidates"); } const previousStatus = election.status; election.status = ElectionStatus.ACTIVE; this.electionRepository.update(election); // Observer pattern: notify all subscribers of the state change this.eventEmitter.notify(election, previousStatus, ElectionStatus.ACTIVE);}
Activating an election triggers the Observer pattern, notifying all registered event listeners. This can be used to send notifications, update dashboards, or trigger other automated processes.
Close an election to stop accepting votes and make results available.
// From src/services/ElectionService.ts:184-196closeElection(electionID: string): void { const election = this.electionRepository.findById(electionID); if (!election) { throw new Error("Election not found"); } const previousStatus = election.status; election.status = ElectionStatus.CLOSED; this.electionRepository.update(election); // Observer pattern: notify all subscribers of the state change this.eventEmitter.notify(election, previousStatus, ElectionStatus.CLOSED);}
Closing an election is permanent. Once closed, the election cannot be reopened or accept new votes.
// From src/services/ElectionService.ts:201-218deleteElection(electionID: string): void { const election = this.electionRepository.findById(electionID); if (!election) { throw new Error("Election not found"); } if (election.status !== ElectionStatus.DRAFT) { throw new Error("Only draft elections can be deleted"); } // Delete all candidates first const candidates = this.candidateRepository.findByElectionId(electionID); for (const candidate of candidates) { this.candidateRepository.delete(candidate.candidateID); } this.electionRepository.delete(electionID);}
Deleting an election also deletes all associated candidates. This action cannot be undone.
Why deletion is restricted:
ACTIVE elections have voters currently participating
CLOSED elections have cast ballots and published results
Deleting these would compromise election integrity
The Observer pattern is triggered when elections transition to ACTIVE or CLOSED status, allowing you to implement custom business logic without modifying the core election service.