Skip to content

Commit 23f65fd

Browse files
committed
Added: get_detailed_result method and route
1 parent edd7b24 commit 23f65fd

2 files changed

Lines changed: 96 additions & 0 deletions

File tree

app/auth.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,45 @@ def create_admin_token(
4646
settings.secret,
4747
algorithm="HS256",
4848
)
49+
50+
def create_election_iam_token(
51+
election_ref: str,
52+
iam:str|list[str]
53+
) -> str:
54+
if isinstance(iam, str):
55+
iam = [iam]
56+
57+
return jws.sign(
58+
{"iam": iam, "election": election_ref},
59+
settings.secret,
60+
algorithm="HS256",
61+
)
62+
63+
# IAM
64+
65+
def can(iam:str|list[str], action:str) -> bool:
66+
"""
67+
Check if the user has the right to perform the action
68+
"""
69+
if isinstance(iam, str):
70+
iam = [iam]
71+
72+
if action not in SPECIFIC_IAM_TO_ALLOWED:
73+
return False
74+
75+
if action in iam:
76+
return True
77+
78+
return any(instance in SPECIFIC_IAM_TO_ALLOWED[action] for instance in iam)
79+
80+
ADMIN= "admin"
81+
GET_PROGRESS = "get_progress"
82+
CAN_VIEW_RESULT = "can_view_result"
83+
CAN_VIEW_DETAILED_RESULT = "can_view_detailed_result"
84+
85+
SPECIFIC_IAM_TO_ALLOWED = {
86+
ADMIN : [],
87+
GET_PROGRESS : [ADMIN],
88+
CAN_VIEW_RESULT : [ADMIN, CAN_VIEW_DETAILED_RESULT],
89+
CAN_VIEW_DETAILED_RESULT : [ADMIN]
90+
}

app/crud.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,60 @@ def get_ballot(db: Session, token: str) -> schemas.BallotGet:
547547
return schemas.BallotGet(token=token, votes=votes_get, election=election)
548548

549549

550+
def get_detailed_results(db:Session, election_ref:str, token: str) -> str:
551+
payload = jws_verify(token)
552+
553+
if payload["election"] != election_ref:
554+
raise errors.UnauthorizedError("Wrong authentication for this election")
555+
556+
db_election = get_election(db, election_ref)
557+
votes = db_election.votes
558+
candidates = db_election.candidates
559+
candidates_count = len(candidates)
560+
filtered_votes = [v for v in votes if v.candidate_id is not None and v.grade_id is not None]
561+
vote_count = len(filtered_votes)
562+
filtered_votes.sort(key=lambda v: v.id)
563+
564+
if vote_count % candidates_count != 0:
565+
raise errors.ForbiddenError("The number of votes is not a multiple of the number of candidates")
566+
567+
# Build a table header: first cell empty, then candidate names
568+
header = [""] + [c.name for c in candidates]
569+
table = []
570+
571+
# Group votes by voter (each group has candidates_count votes)
572+
for i in range(vote_count // candidates_count):
573+
group = filtered_votes[i * candidates_count : (i + 1) * candidates_count]
574+
# Check all votes in group have the same timestamp
575+
timestamps = {v.date_created for v in group}
576+
577+
if len(timestamps) != 1:
578+
raise errors.ForbiddenError("Votes in a ballot must have the same timestamp")
579+
580+
# Check all candidates are present
581+
candidate_ids = {v.candidate_id for v in group}
582+
expected_ids = {c.id for c in candidates}
583+
584+
if candidate_ids != expected_ids:
585+
raise errors.ForbiddenError("Each ballot must contain all candidates")
586+
587+
# Build row: first cell is timestamp, then grade label for each candidate
588+
row = ['-']
589+
590+
for c in candidates:
591+
vote = next(v for v in group if v.candidate_id == c.id)
592+
# Find grade label from header/table
593+
grade = next((g for g in db_election.grades if g.id == vote.grade_id), None)
594+
row.append(grade.label if grade else "")
595+
596+
table.insert(random.randint(0, len(table)), row)
597+
598+
table.insert(0, header)
599+
return table
600+
601+
602+
603+
550604
def get_results(db: Session, election_ref: str, token: t.Optional[str]) -> schemas.ResultsGet:
551605
db_election = get_election(db, election_ref)
552606
if db_election is None:

0 commit comments

Comments
 (0)