From cad5cfe6363e659af18f7d4066c9fbc1215f2384 Mon Sep 17 00:00:00 2001 From: dylan Date: Sat, 11 Jan 2025 18:29:01 +0100 Subject: [PATCH] Move voting to a new endpoint /election/{id}/votes --- cmd/web/handlers.go | 49 +++++++++++++++------------- cmd/web/handlers_test.go | 63 +++++++++++++++++++++++------------- cmd/web/helpers.go | 19 +++-------- cmd/web/openapi.yml | 70 +++++++++++++++++++++++----------------- cmd/web/routes.go | 2 +- go.mod | 8 +++-- go.sum | 20 ++++++++++-- internal/generated.go | 55 ++++++++++++++++++++++++++++--- 8 files changed, 186 insertions(+), 100 deletions(-) diff --git a/cmd/web/handlers.go b/cmd/web/handlers.go index 3b057e2..ede1f96 100644 --- a/cmd/web/handlers.go +++ b/cmd/web/handlers.go @@ -11,6 +11,7 @@ import ( "math/rand" "net/http" "slices" + "strconv" "time" ) @@ -111,8 +112,6 @@ type createVotesRequestWithValidator struct { } func (r *createVotesRequestWithValidator) isValid() bool { - r.CheckField(validator.GreaterThan(r.ElectionId, 0), "electionId", "must be greater than 0") - for _, choice := range r.Choices { r.CheckField(validator.NotBlank(choice.ChoiceText), "choiceText", "must not be blank") } @@ -133,10 +132,16 @@ func (app *application) createVotes(w http.ResponseWriter, r *http.Request) { return } - election, err := app.elections.GetById(request.ElectionId) // get from path instead of from the JSON + electionID, err := strconv.Atoi(r.PathValue("id")) + if err != nil { + app.clientError(w, http.StatusBadRequest, "Couldn't convert the id you provided to a number") + return + } + + election, err := app.elections.GetById(electionID) if err != nil { if errors.Is(err, sql.ErrNoRows) { - app.unprocessableEntityErrorSingle(w, fmt.Errorf("election with id %v doesn't exist", request.ElectionId)) // TODO: return 404 + app.clientError(w, http.StatusNotFound, "Couldn't find an election with the ID you provided") return } app.serverError(w, r, err) @@ -145,7 +150,7 @@ func (app *application) createVotes(w http.ResponseWriter, r *http.Request) { for _, c := range request.Choices { choiceExists := slices.Contains(election.Choices, c.ChoiceText) if !choiceExists { - app.unprocessableEntityErrorSingle(w, fmt.Errorf("choice %v doesn't exist", c.ChoiceText)) + app.clientError(w, http.StatusUnprocessableEntity, fmt.Sprintf("choice %v doesn't exist", c.ChoiceText)) return } } @@ -156,13 +161,13 @@ func (app *application) createVotes(w http.ResponseWriter, r *http.Request) { } if tokensUsed > election.Tokens { - app.unprocessableEntityErrorSingle(w, fmt.Errorf("you used too many tokens; must not exceed %v tokens", election.Tokens)) + app.clientError(w, http.StatusUnprocessableEntity, fmt.Sprintf("you used too many tokens; must not exceed %v tokens", election.Tokens)) return } electionHasExpired := election.ExpiresAt.Before(time.Now()) if electionHasExpired { - app.unprocessableEntityErrorSingle(w, fmt.Errorf("election has expired")) + app.clientError(w, http.StatusUnprocessableEntity, "election has expired") return } @@ -193,9 +198,9 @@ func (app *application) createVotes(w http.ResponseWriter, r *http.Request) { func (app *application) createVotesHandleKnownVotersElection(w http.ResponseWriter, r *http.Request, request createVotesRequestWithValidator, election *models.Election) (string, error) { if request.VoterIdentity == nil || validator.Blank(*request.VoterIdentity) { - err := fmt.Errorf("election has known voters; you must provide an identity provided by the organizer") - app.unprocessableEntityErrorSingle(w, err) - return "", err + message := "election has known voters; you must provide an identity provided by the organizer" + app.clientError(w, http.StatusUnprocessableEntity, message) + return "", fmt.Errorf(message) } voterIdentity := *request.VoterIdentity @@ -205,9 +210,9 @@ func (app *application) createVotesHandleKnownVotersElection(w http.ResponseWrit return "", err } if hasCastVotes { - err := fmt.Errorf("you already voted") - app.unprocessableEntityErrorSingle(w, err) - return "", err + message := "you already voted" + app.clientError(w, http.StatusUnprocessableEntity, message) + return "", fmt.Errorf(message) } voterExists, err := app.voters.Exists(voterIdentity, election.ID) @@ -216,9 +221,9 @@ func (app *application) createVotesHandleKnownVotersElection(w http.ResponseWrit return "", err } if !voterExists { - err := fmt.Errorf("invalid voter identity") - app.unprocessableEntityErrorSingle(w, err) - return "", err + message := "invalid voter identity" + app.clientError(w, http.StatusUnprocessableEntity, message) + return "", fmt.Errorf(message) } return voterIdentity, nil @@ -233,9 +238,9 @@ func (app *application) createVotesHandleUnknownVotersElection(w http.ResponseWr return "", err } if voterExists { - err := fmt.Errorf("you already voted") - app.unprocessableEntityErrorSingle(w, err) - return "", err + message := "you already voted" + app.clientError(w, http.StatusUnprocessableEntity, message) + return "", fmt.Errorf(message) } _, err = app.voters.Insert(voterIdentity, election.ID) @@ -250,9 +255,9 @@ func (app *application) createVotesHandleUnknownVotersElection(w http.ResponseWr return "", err } if voterCount == election.MaxVoters { - err := fmt.Errorf("maximum voters reached") - app.unprocessableEntityErrorSingle(w, err) - return "", err + message := "maximum voters reached" + app.clientError(w, http.StatusUnprocessableEntity, message) + return "", fmt.Errorf(message) } return voterIdentity, nil diff --git a/cmd/web/handlers_test.go b/cmd/web/handlers_test.go index 26951fa..0ad6222 100644 --- a/cmd/web/handlers_test.go +++ b/cmd/web/handlers_test.go @@ -254,7 +254,7 @@ func TestCreateVotes_UnknownVotersElection(t *testing.T) { On("Insert", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return(1, nil) - path := "/votes" + path := "/election/1/votes" tests := []struct { name string @@ -273,7 +273,6 @@ func TestCreateVotes_UnknownVotersElection(t *testing.T) { {ChoiceText: "Gandhi", Tokens: 60}, {ChoiceText: "Buddha", Tokens: 40}, }, - ElectionId: 1, VoterIdentity: nil, }, expectedCode: http.StatusCreated, @@ -289,7 +288,6 @@ func TestCreateVotes_UnknownVotersElection(t *testing.T) { {ChoiceText: "Gandhi", Tokens: 60}, {ChoiceText: "Buddha", Tokens: 41}, }, - ElectionId: 1, VoterIdentity: nil, }, expectedCode: http.StatusUnprocessableEntity, @@ -305,7 +303,6 @@ func TestCreateVotes_UnknownVotersElection(t *testing.T) { {ChoiceText: "Gandhi", Tokens: 60}, {ChoiceText: "Buddh", Tokens: 40}, }, - ElectionId: 1, VoterIdentity: nil, }, expectedCode: http.StatusUnprocessableEntity, @@ -362,7 +359,7 @@ func TestCreateVotes_KnownVotersElection(t *testing.T) { On("Exists", mock.Anything, mock.Anything). Return(false, nil) - path := "/votes" + path := "/election/1/votes" tests := []struct { name string @@ -381,7 +378,6 @@ func TestCreateVotes_KnownVotersElection(t *testing.T) { {ChoiceText: "Gandhi", Tokens: 60}, {ChoiceText: "Buddha", Tokens: 40}, }, - ElectionId: 1, VoterIdentity: &EXISTING_VOTER_IDENTITY, }, expectedCode: http.StatusCreated, @@ -397,7 +393,6 @@ func TestCreateVotes_KnownVotersElection(t *testing.T) { {ChoiceText: "Gandhi", Tokens: 60}, {ChoiceText: "Buddha", Tokens: 40}, }, - ElectionId: 1, VoterIdentity: &NON_EXISTING_VOTER_IDENTITY, }, expectedCode: http.StatusUnprocessableEntity, @@ -413,7 +408,6 @@ func TestCreateVotes_KnownVotersElection(t *testing.T) { {ChoiceText: "Gandhi", Tokens: 60}, {ChoiceText: "Buddha", Tokens: 40}, }, - ElectionId: 1, VoterIdentity: nil, }, expectedCode: http.StatusUnprocessableEntity, @@ -429,7 +423,6 @@ func TestCreateVotes_KnownVotersElection(t *testing.T) { {ChoiceText: "Gandhi", Tokens: 60}, {ChoiceText: "Buddha", Tokens: 41}, }, - ElectionId: 1, VoterIdentity: &EXISTING_VOTER_IDENTITY, }, expectedCode: http.StatusUnprocessableEntity, @@ -445,7 +438,6 @@ func TestCreateVotes_KnownVotersElection(t *testing.T) { {ChoiceText: "Gandhi", Tokens: 60}, {ChoiceText: "Buddh", Tokens: 40}, }, - ElectionId: 1, VoterIdentity: &EXISTING_VOTER_IDENTITY, }, expectedCode: http.StatusUnprocessableEntity, @@ -475,7 +467,7 @@ func TestCreateVotes_NonExistingElection(t *testing.T) { On("GetById", mock.Anything). Return((*models.Election)(nil), sql.ErrNoRows) - path := "/votes" + path := "/election/1/votes" requestBody := api.CreateVotesRequest{ Choices: []struct { ChoiceText string `json:"choiceText"` @@ -484,7 +476,6 @@ func TestCreateVotes_NonExistingElection(t *testing.T) { {ChoiceText: "Gandhi", Tokens: 60}, {ChoiceText: "Buddha", Tokens: 40}, }, - ElectionId: 1, VoterIdentity: nil, } requestBodyJson, err := json.Marshal(requestBody) @@ -494,7 +485,38 @@ func TestCreateVotes_NonExistingElection(t *testing.T) { code, _, _ := server.post(t, path, bytes.NewReader(requestBodyJson)) - assert.Equal(t, http.StatusUnprocessableEntity, code) + assert.Equal(t, http.StatusNotFound, code) +} + +func TestCreateVotes_NonNumberElectionID(t *testing.T) { + app := newTestApplication(t) + server := newTestServer(t, app.routes()) + defer server.Close() + + mockElections := app.elections.(*mockElectionModel) + mockElections. + On("GetById", mock.Anything). + Return((*models.Election)(nil), sql.ErrNoRows) + + path := "/election/1a/votes" + requestBody := api.CreateVotesRequest{ + Choices: []struct { + ChoiceText string `json:"choiceText"` + Tokens int `json:"tokens"` + }{ + {ChoiceText: "Gandhi", Tokens: 60}, + {ChoiceText: "Buddha", Tokens: 40}, + }, + VoterIdentity: nil, + } + requestBodyJson, err := json.Marshal(requestBody) + if err != nil { + t.Fatal(err) + } + + code, _, _ := server.post(t, path, bytes.NewReader(requestBodyJson)) + + assert.Equal(t, http.StatusBadRequest, code) } func TestCreateVotes_AlreadyVoted(t *testing.T) { @@ -533,7 +555,8 @@ func TestCreateVotes_AlreadyVoted(t *testing.T) { On("Exists", mock.Anything, mock.Anything). Return(true, nil) - path := "/votes" + existingElectionPath := "/election/1/votes" + nonExistingElectionPath := "/election/1/votes" voterIdentity := "anything" tests := []struct { @@ -544,7 +567,7 @@ func TestCreateVotes_AlreadyVoted(t *testing.T) { }{ { name: "Invalid request for known voters election (already voted)", - urlPath: path, + urlPath: existingElectionPath, body: api.CreateVotesRequest{ Choices: []struct { ChoiceText string `json:"choiceText"` @@ -553,14 +576,13 @@ func TestCreateVotes_AlreadyVoted(t *testing.T) { {ChoiceText: "Gandhi", Tokens: 60}, {ChoiceText: "Buddha", Tokens: 40}, }, - ElectionId: 1, VoterIdentity: &voterIdentity, }, expectedCode: http.StatusUnprocessableEntity, }, { name: "Invalid request for unknown voters election (already voted)", - urlPath: path, + urlPath: nonExistingElectionPath, body: api.CreateVotesRequest{ Choices: []struct { ChoiceText string `json:"choiceText"` @@ -569,7 +591,6 @@ func TestCreateVotes_AlreadyVoted(t *testing.T) { {ChoiceText: "Gandhi", Tokens: 60}, {ChoiceText: "Buddha", Tokens: 40}, }, - ElectionId: 2, VoterIdentity: &voterIdentity, }, expectedCode: http.StatusUnprocessableEntity, @@ -619,7 +640,7 @@ func TestCreateVotes_UnknownVotersElectionMaxVotersReached(t *testing.T) { On("CountByElection", mock.Anything). Return(10, nil) - path := "/votes" + path := "/election/1/votes" requestBody := api.CreateVotesRequest{ Choices: []struct { ChoiceText string `json:"choiceText"` @@ -628,7 +649,6 @@ func TestCreateVotes_UnknownVotersElectionMaxVotersReached(t *testing.T) { {ChoiceText: "Gandhi", Tokens: 60}, {ChoiceText: "Buddha", Tokens: 40}, }, - ElectionId: 1, VoterIdentity: nil, } requestBodyJson, err := json.Marshal(requestBody) @@ -671,7 +691,7 @@ func TestCreateVotes_ExpiredElection(t *testing.T) { On("CountByElection", mock.Anything). Return(10, nil) - path := "/votes" + path := "/election/1/votes" requestBody := api.CreateVotesRequest{ Choices: []struct { ChoiceText string `json:"choiceText"` @@ -680,7 +700,6 @@ func TestCreateVotes_ExpiredElection(t *testing.T) { {ChoiceText: "Gandhi", Tokens: 60}, {ChoiceText: "Buddha", Tokens: 40}, }, - ElectionId: 1, VoterIdentity: nil, } requestBodyJson, err := json.Marshal(requestBody) diff --git a/cmd/web/helpers.go b/cmd/web/helpers.go index fb74545..ae1aedd 100644 --- a/cmd/web/helpers.go +++ b/cmd/web/helpers.go @@ -22,11 +22,9 @@ func (app *application) serverError(w http.ResponseWriter, r *http.Request, err func (app *application) clientError(w http.ResponseWriter, status int, message string) { w.WriteHeader(status) var response = api.ErrorResponse{ - Code: http.StatusUnprocessableEntity, - Details: &map[string]interface{}{ - "error": message, - }, - Message: "There was an error in the request", + Code: status, + Details: nil, + Message: message, } json.NewEncoder(w).Encode(response) } @@ -38,16 +36,7 @@ func (app *application) unprocessableEntityError(w http.ResponseWriter, v valida Details: &map[string]interface{}{ "fields": v.FieldErrors, }, - Message: "Election data is invalid", - } - json.NewEncoder(w).Encode(response) -} - -func (app *application) unprocessableEntityErrorSingle(w http.ResponseWriter, err error) { - w.WriteHeader(http.StatusUnprocessableEntity) - var response = api.ErrorResponse{ - Code: http.StatusUnprocessableEntity, - Message: err.Error(), + Message: "Request data is invalid", } json.NewEncoder(w).Encode(response) } diff --git a/cmd/web/openapi.yml b/cmd/web/openapi.yml index dcc034a..caf20d8 100644 --- a/cmd/web/openapi.yml +++ b/cmd/web/openapi.yml @@ -58,6 +58,46 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" + /election/{id}/votes: + post: + tags: + - vote + summary: Cast your votes for an election + operationId: createVotes + parameters: + - name: id + in: path + required: true + description: The ID of the election + schema: + type: integer + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreateVotesRequest" + responses: + 200: + description: Votes cast + 404: + description: Election not found + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + 422: + description: Unprocessable content + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + 500: + description: Server error + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + /election/{id}/results: get: tags: @@ -90,33 +130,6 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" - /votes: - post: - tags: - - vote - summary: Cast your votes for an election - operationId: createVotes - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/CreateVotesRequest" - responses: - 200: - description: Votes cast - 422: - description: Unprocessable content - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - 500: - description: Server error - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - components: schemas: Election: @@ -266,14 +279,11 @@ components: CreateVotesRequest: type: object required: - - electionId - choices properties: voterIdentity: type: string description: Must be filled if election has known voters - electionId: - type: integer choices: type: array items: diff --git a/cmd/web/routes.go b/cmd/web/routes.go index 3dd6def..0ffedb9 100644 --- a/cmd/web/routes.go +++ b/cmd/web/routes.go @@ -18,7 +18,7 @@ func (app *application) routes() http.Handler { mux := http.NewServeMux() mux.HandleFunc("POST /election", app.createElection) - mux.HandleFunc("POST /votes", app.createVotes) + mux.HandleFunc("POST /election/{id}/votes", app.createVotes) standard := alice.New(app.recoverPanic, app.logRequest) return standard.Then(mux) diff --git a/go.mod b/go.mod index 25643b3..64aef27 100644 --- a/go.mod +++ b/go.mod @@ -7,15 +7,18 @@ require ( github.com/golang-migrate/migrate/v4 v4.18.1 github.com/justinas/alice v1.2.0 github.com/mattn/go-sqlite3 v1.14.24 + github.com/oapi-codegen/runtime v1.1.1 github.com/stretchr/testify v1.9.0 ) require ( github.com/Eun/go-convert v0.0.0-20200421145326-bef6c56666ee // indirect github.com/Eun/go-doppelgangerreader v0.0.0-20190911075941-30f1527f16b2 // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/araddon/dateparse v0.0.0-20200409225146-d820a6159ab1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/go-cmp v0.5.6 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gookit/color v1.4.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -24,8 +27,8 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/k0kubun/pp v3.0.1+incompatible // indirect github.com/lunixbochs/vtclean v1.0.0 // indirect - github.com/mattn/go-colorable v0.1.7 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -35,5 +38,6 @@ require ( go.uber.org/atomic v1.7.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3bfa790..58786c1 100644 --- a/go.sum +++ b/go.sum @@ -7,11 +7,15 @@ github.com/Eun/go-hit v0.5.23/go.mod h1:LCHZ6WSPFDXlTQkFUSLe0VsrOhzzEEzbPzCGc6FY github.com/Eun/go-testdoc v0.0.1/go.mod h1:uT+GeDi7TpqQx6MBkcfXD9nF15Q8IX+kTNEnUUPbuUo= github.com/Eun/yaegi-template v1.5.16/go.mod h1:eyFQ1QHbKLNHKpUvdjt8+99ZR1ji7lVVbduSK1M5N/U= github.com/Eun/yaegi-template v1.5.18/go.mod h1:iVHjge496SWL7hLf1euBZIO40Bk0R38g6lu8iyvpc30= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/aaw/maybe_tls v0.0.0-20160803104303-89c499bcc6aa h1:6yJyU8MlPBB2enGJdPciPlr8P+PC0nhCFHnSHYMirZI= github.com/aaw/maybe_tls v0.0.0-20160803104303-89c499bcc6aa/go.mod h1:I0wzMZvViQzmJjxK+AtfFAnqDCkQV/+r17PO1CCSYnU= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI= github.com/araddon/dateparse v0.0.0-20200409225146-d820a6159ab1 h1:TEBmxO80TM04L8IuMWk77SGL1HomBmKTdzdJLLWznxI= github.com/araddon/dateparse v0.0.0-20200409225146-d820a6159ab1/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/dave/jennifer v1.4.1/go.mod h1:7jEdnm+qBcxl8PC0zyp7vxcpSRnzXSt9r39tpTVGlwA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -22,6 +26,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gookit/color v1.4.2 h1:tXy44JFSFkKnELV6WaMo/lLfu/meqITX3iAV52do7lk= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= @@ -39,6 +45,7 @@ github.com/itchyny/timefmt-go v0.1.3 h1:7M3LGVDsqcd0VZH2U+x393obrzZisp7C0uEe921i github.com/itchyny/timefmt-go v0.1.3/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo= github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= @@ -54,12 +61,14 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= -github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= @@ -70,9 +79,12 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= +github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= @@ -117,6 +129,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -136,8 +149,9 @@ golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSm golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/generated.go b/internal/generated.go index 7c6196f..d88fba8 100644 --- a/internal/generated.go +++ b/internal/generated.go @@ -9,6 +9,8 @@ import ( "fmt" "net/http" "time" + + "github.com/oapi-codegen/runtime" ) // CreateElectionRequest defines model for CreateElectionRequest. @@ -34,12 +36,14 @@ type CreateVotesRequest struct { ChoiceText string `json:"choiceText"` Tokens int `json:"tokens"` } `json:"choices"` - ElectionId int `json:"electionId"` // VoterIdentity Must be filled if election has known voters VoterIdentity *string `json:"voterIdentity,omitempty"` } +// ElectionResultsResponse defines model for ElectionResultsResponse. +type ElectionResultsResponse = map[string]interface{} + // ErrorResponse defines model for ErrorResponse. type ErrorResponse struct { // Code Machine-readable error code @@ -63,9 +67,12 @@ type ServerInterface interface { // Create a new election // (POST /election) CreateElection(w http.ResponseWriter, r *http.Request) + // Get the results of an election + // (GET /election/{id}/results) + GetElectionIdResults(w http.ResponseWriter, r *http.Request, id int) // Cast your votes for an election - // (POST /votes) - CreateVotes(w http.ResponseWriter, r *http.Request) + // (POST /election/{id}/votes) + CreateVotes(w http.ResponseWriter, r *http.Request, id int) } // ServerInterfaceWrapper converts contexts to parameters. @@ -92,12 +99,49 @@ func (siw *ServerInterfaceWrapper) CreateElection(w http.ResponseWriter, r *http handler.ServeHTTP(w, r.WithContext(ctx)) } +// GetElectionIdResults operation middleware +func (siw *ServerInterfaceWrapper) GetElectionIdResults(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "id" ------------- + var id int + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetElectionIdResults(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // CreateVotes operation middleware func (siw *ServerInterfaceWrapper) CreateVotes(w http.ResponseWriter, r *http.Request) { ctx := r.Context() + var err error + + // ------------- Path parameter "id" ------------- + var id int + + err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CreateVotes(w, r) + siw.Handler.CreateVotes(w, r, id) })) for _, middleware := range siw.HandlerMiddlewares { @@ -222,7 +266,8 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H } m.HandleFunc("POST "+options.BaseURL+"/election", wrapper.CreateElection) - m.HandleFunc("POST "+options.BaseURL+"/votes", wrapper.CreateVotes) + m.HandleFunc("GET "+options.BaseURL+"/election/{id}/results", wrapper.GetElectionIdResults) + m.HandleFunc("POST "+options.BaseURL+"/election/{id}/votes", wrapper.CreateVotes) return m }