From c095e9ae0b0feb38e1beff5043e8bc12f1c43ee5 Mon Sep 17 00:00:00 2001 From: dylan Date: Tue, 14 Jan 2025 16:52:20 +0100 Subject: [PATCH] Implement getElectionResults --- cmd/web/handlers.go | 37 +++++++++++++++++++++++++++++ cmd/web/openapi.yml | 22 ++++++++++++++++++ internal/generated.go | 10 +++++++- internal/models/votes.go | 50 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 1 deletion(-) diff --git a/cmd/web/handlers.go b/cmd/web/handlers.go index fe23b3c..90303e8 100644 --- a/cmd/web/handlers.go +++ b/cmd/web/handlers.go @@ -9,6 +9,7 @@ import ( "encoding/json" "errors" "fmt" + "math" "math/rand" "net/http" "slices" @@ -271,5 +272,41 @@ func (app *application) createVotesHandleUnknownVotersElection(w http.ResponseWr } func (app *application) GetElectionResults(w http.ResponseWriter, r *http.Request, id int) { + votes, err := app.votes.GetByElection(id) + if err != nil { + if errors.Is(sql.ErrNoRows, err) { + app.clientError(w, http.StatusNotFound, fmt.Sprintf("couldn't find votes for an election with id %v", id)) + return + } + app.serverError(w, r, err) + return + } + results := getResultsFromVotes(votes) + + response, err := json.Marshal(api.ElectionResultsResponse{Results: &results}) + if err != nil { + app.serverError(w, r, err) + return + } + + w.Write(response) +} + +func getResultsFromVotes(votes *[]models.Vote) []api.VotesForChoice { + votesForChoice := make(map[string]int) + for _, v := range *votes { + votesForChoice[v.ChoiceText] += int(math.Floor(math.Sqrt(float64(v.Tokens)))) + } + + results := make([]api.VotesForChoice, 0) + for choice, votes := range votesForChoice { + result := api.VotesForChoice{ + Choice: choice, + Votes: votes, + } + results = append(results, result) + } + + return results } diff --git a/cmd/web/openapi.yml b/cmd/web/openapi.yml index b8b888a..b8de6e6 100644 --- a/cmd/web/openapi.yml +++ b/cmd/web/openapi.yml @@ -124,6 +124,12 @@ paths: application/json: schema: $ref: "#/components/schemas/ElectionResultsResponse" + 400: + description: Request malformed + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" 404: description: Election doesn't exist content: @@ -205,6 +211,22 @@ components: ElectionResultsResponse: type: object + properties: + results: + type: array + items: + $ref: "#/components/schemas/VotesForChoice" + + VotesForChoice: + type: object + required: + - choice + - votes + properties: + choice: + type: string + votes: + type: integer ErrorResponse: type: object diff --git a/internal/generated.go b/internal/generated.go index 8da6460..098df8e 100644 --- a/internal/generated.go +++ b/internal/generated.go @@ -42,7 +42,9 @@ type CreateVotesRequest struct { } // ElectionResultsResponse defines model for ElectionResultsResponse. -type ElectionResultsResponse = map[string]interface{} +type ElectionResultsResponse struct { + Results *[]VotesForChoice `json:"results,omitempty"` +} // ErrorResponse defines model for ErrorResponse. type ErrorResponse struct { @@ -56,6 +58,12 @@ type ErrorResponse struct { Message string `json:"message"` } +// VotesForChoice defines model for VotesForChoice. +type VotesForChoice struct { + Choice string `json:"choice"` + Votes int `json:"votes"` +} + // CreateElectionJSONRequestBody defines body for CreateElection for application/json ContentType. type CreateElectionJSONRequestBody = CreateElectionRequest diff --git a/internal/models/votes.go b/internal/models/votes.go index 513d762..a6e7ad0 100644 --- a/internal/models/votes.go +++ b/internal/models/votes.go @@ -3,17 +3,27 @@ package models import ( "database/sql" "errors" + "time" ) type VoteModelInterface interface { Insert(voterIdentity string, electionId int, choiceText string, tokens int) (int, error) Exists(voterIdentity string, electionID int) (bool, error) + GetByElection(electionID int) (*[]Vote, error) } type VoteModel struct { DB *sql.DB } +type Vote struct { + VoterIdentity string + ElectionID int + ChoiceText string + Tokens int + CreatedAt time.Time +} + func (v *VoteModel) Insert(voterIdentity string, electionId int, choiceText string, tokens int) (int, error) { tx, err := v.DB.Begin() if err != nil { @@ -55,3 +65,43 @@ func (v *VoteModel) Exists(voterIdentity string, electionID int) (bool, error) { } return exists, nil } + +func (v *VoteModel) GetByElection(electionID int) (*[]Vote, error) { + query := ` + SELECT voter_identity, election_id, choice_text, tokens, created_at + FROM votes + WHERE election_id = ? + ` + + rows, err := v.DB.Query(query, electionID) + if err != nil { + return nil, err + } + defer rows.Close() + + var votes []Vote + for rows.Next() { + var vote Vote + err := rows.Scan( + &vote.VoterIdentity, + &vote.ElectionID, + &vote.ChoiceText, + &vote.Tokens, + &vote.CreatedAt, + ) + if err != nil { + return nil, err + } + votes = append(votes, vote) + } + + if err = rows.Err(); err != nil { + return nil, err + } + + if len(votes) == 0 { + return nil, sql.ErrNoRows + } + + return &votes, nil +}