From 5668b1cd6a6c2d9c8d8385e34d74870198760ed0 Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 20 Jan 2025 10:04:15 +0100 Subject: [PATCH] Insert election with uuid instead of auto-generated id --- cmd/web/handlers.go | 6 +- cmd/web/handlers_test.go | 56 ++-- cmd/web/main.go | 2 - cmd/web/main_integration.go | 281 --------------------- cmd/web/openapi.yml | 8 +- cmd/web/testutils_test.go | 18 +- internal/generated.go | 14 +- internal/mappers/elections_test.go | 6 +- internal/migrations/sql/000001_init.up.sql | 2 +- internal/models/elections.go | 33 +-- internal/models/voters.go | 12 +- internal/models/votes.go | 14 +- ui/create-election.html | 2 +- 13 files changed, 94 insertions(+), 360 deletions(-) delete mode 100644 cmd/web/main_integration.go diff --git a/cmd/web/handlers.go b/cmd/web/handlers.go index 638591d..928ea06 100644 --- a/cmd/web/handlers.go +++ b/cmd/web/handlers.go @@ -145,7 +145,7 @@ func (r *createVotesRequestWithValidator) isValid() bool { return r.Valid() } -func (app *application) CreateVotes(w http.ResponseWriter, r *http.Request, id int) { +func (app *application) CreateVotes(w http.ResponseWriter, r *http.Request, id string) { var request createVotesRequestWithValidator if err := app.unmarshalRequest(r, &request); err != nil { @@ -284,7 +284,7 @@ func (app *application) createVotesHandleUnknownVotersElection(w http.ResponseWr return voterIdentity, nil } -func (app *application) GetElectionResults(w http.ResponseWriter, r *http.Request, id int) { +func (app *application) GetElectionResults(w http.ResponseWriter, r *http.Request, id string) { votes, err := app.votes.GetByElection(id) if err != nil { if errors.Is(sql.ErrNoRows, err) { @@ -325,7 +325,7 @@ func getResultsFromVotes(votes *[]models.Vote) []api.VotesForChoice { return results } -func (app *application) GetElection(w http.ResponseWriter, r *http.Request, id int) { +func (app *application) GetElection(w http.ResponseWriter, r *http.Request, id string) { election, err := app.elections.GetById(id) if err != nil { if errors.Is(sql.ErrNoRows, err) { diff --git a/cmd/web/handlers_test.go b/cmd/web/handlers_test.go index 2b39bbc..5d489b5 100644 --- a/cmd/web/handlers_test.go +++ b/cmd/web/handlers_test.go @@ -7,6 +7,7 @@ import ( "database/sql" "encoding/json" "fmt" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "net/http" @@ -21,10 +22,11 @@ func TestCreateElection(t *testing.T) { server := newTestServer(t, app.routes()) defer server.Close() + id, _ := uuid.NewV7() mockElections := app.elections.(*mockElectionModel) mockElections. On("Insert", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). - Return(1, nil) + Return(id.String(), nil) path := baseUri + "election" @@ -197,10 +199,11 @@ func TestCreateElection_KnownVoters(t *testing.T) { server := newTestServer(t, app.routes()) defer server.Close() + electionId, _ := uuid.NewV7() mockElections := app.elections.(*mockElectionModel) mockElections. On("Insert", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). - Return(1, nil) + Return(electionId.String(), nil) mockVoters := app.voters.(*mockVoterModel) mockVoters. @@ -270,11 +273,12 @@ func TestCreateVotes_UnknownVotersElection(t *testing.T) { server := newTestServer(t, app.routes()) defer server.Close() + id, _ := uuid.NewV7() mockElections := app.elections.(*mockElectionModel) mockElections. On("GetById", mock.Anything). Return(&models.Election{ - ID: 1, + ID: id.String(), Name: "Guy of the year", Tokens: 100, AreVotersKnown: false, @@ -373,11 +377,12 @@ func TestCreateVotes_KnownVotersElection(t *testing.T) { server := newTestServer(t, app.routes()) defer server.Close() + id, _ := uuid.NewV7() mockElections := app.elections.(*mockElectionModel) mockElections. On("GetById", mock.Anything). Return(&models.Election{ - ID: 1, + ID: id.String(), Name: "Guy of the year", Tokens: 100, AreVotersKnown: true, @@ -534,7 +539,7 @@ func TestCreateVotes_NonExistingElection(t *testing.T) { assert.Equal(t, http.StatusNotFound, code) } -func TestCreateVotes_NonNumberElectionID(t *testing.T) { +func TestCreateVotes_NonUuidElectionID(t *testing.T) { app := newTestApplication(t) server := newTestServer(t, app.routes()) defer server.Close() @@ -562,7 +567,7 @@ func TestCreateVotes_NonNumberElectionID(t *testing.T) { code, _, _ := server.post(t, path, bytes.NewReader(requestBodyJson)) - assert.Equal(t, http.StatusBadRequest, code) + assert.Equal(t, http.StatusNotFound, code) } func TestCreateVotes_AlreadyVoted(t *testing.T) { @@ -570,8 +575,9 @@ func TestCreateVotes_AlreadyVoted(t *testing.T) { server := newTestServer(t, app.routes()) defer server.Close() + unknownVotersElectionId, _ := uuid.NewV7() unknownVotersElection := models.Election{ - ID: 1, + ID: unknownVotersElectionId.String(), Name: "Guy of the year", Tokens: 100, AreVotersKnown: false, @@ -580,15 +586,18 @@ func TestCreateVotes_AlreadyVoted(t *testing.T) { ExpiresAt: time.Now().Add(24 * time.Hour), Choices: []string{"Gandhi", "Buddha"}, } + knownVotersElection := unknownVotersElection + knownVotersElectionId, _ := uuid.NewV7() + knownVotersElection.ID = knownVotersElectionId.String() knownVotersElection.AreVotersKnown = true mockElections := app.elections.(*mockElectionModel) mockElections. - On("GetById", 1). + On("GetById", knownVotersElectionId.String()). Return(&knownVotersElection, nil) mockElections. - On("GetById", 2). + On("GetById", unknownVotersElectionId.String()). Return(&unknownVotersElection, nil) mockVotes := app.votes.(*mockVoteModel) @@ -601,8 +610,9 @@ func TestCreateVotes_AlreadyVoted(t *testing.T) { On("Exists", mock.Anything, mock.Anything). Return(true, nil) - knownVotersElectionPath := baseUri + "election/1/votes" - unknownVotersElectionPath := baseUri + "election/2/votes" + layout := baseUri + "election/%v/votes" + knownVotersElectionPath := fmt.Sprintf(layout, unknownVotersElectionId) + unknownVotersElectionPath := fmt.Sprintf(layout, knownVotersElectionId) voterIdentity := "anything" tests := []struct { @@ -661,11 +671,12 @@ func TestCreateVotes_UnknownVotersElectionMaxVotersReached(t *testing.T) { server := newTestServer(t, app.routes()) defer server.Close() + id, _ := uuid.NewV7() mockElections := app.elections.(*mockElectionModel) mockElections. On("GetById", mock.Anything). Return(&models.Election{ - ID: 1, + ID: id.String(), Name: "Guy of the year", Tokens: 100, AreVotersKnown: false, @@ -712,11 +723,12 @@ func TestCreateVotes_ExpiredElection(t *testing.T) { server := newTestServer(t, app.routes()) defer server.Close() + id, _ := uuid.NewV7() mockElections := app.elections.(*mockElectionModel) mockElections. On("GetById", mock.Anything). Return(&models.Election{ - ID: 1, + ID: id.String(), Name: "Guy of the year", Tokens: 100, AreVotersKnown: false, @@ -763,38 +775,39 @@ func TestGetElectionResults(t *testing.T) { server := newTestServer(t, app.routes()) defer server.Close() + electionID, _ := uuid.NewV7() votes := []models.Vote{ { VoterIdentity: "Voter1", - ElectionID: 1, + ElectionID: electionID.String(), ChoiceText: "Choice1", Tokens: 2, CreatedAt: time.Now(), }, { VoterIdentity: "Voter2", - ElectionID: 1, + ElectionID: electionID.String(), ChoiceText: "Choice2", Tokens: 4, CreatedAt: time.Now(), }, { VoterIdentity: "Voter3", - ElectionID: 1, + ElectionID: electionID.String(), ChoiceText: "Choice3", Tokens: 6, CreatedAt: time.Now(), }, { VoterIdentity: "Voter4", - ElectionID: 1, + ElectionID: electionID.String(), ChoiceText: "Choice1", Tokens: 8, CreatedAt: time.Now(), }, { VoterIdentity: "Voter5", - ElectionID: 1, + ElectionID: electionID.String(), ChoiceText: "Choice2", Tokens: 10, CreatedAt: time.Now(), @@ -802,10 +815,10 @@ func TestGetElectionResults(t *testing.T) { } mockVotes := app.votes.(*mockVoteModel) mockVotes. - On("GetByElection", mock.Anything). + On("GetByElection", electionID.String()). Return(&votes, nil) - path := baseUri + "election/1/results" + path := baseUri + fmt.Sprintf("election/%v/results", electionID) code, _, body := server.get(t, path) assert.Equal(t, http.StatusOK, code) @@ -857,11 +870,12 @@ func TestGetElection_Found(t *testing.T) { server := newTestServer(t, app.routes()) defer server.Close() + id, _ := uuid.NewV7() mockElections := app.elections.(*mockElectionModel) mockElections. On("GetById", mock.Anything). Return(&models.Election{ - ID: 1, + ID: id.String(), Name: "Guy of the year", Tokens: 100, AreVotersKnown: false, diff --git a/cmd/web/main.go b/cmd/web/main.go index 2284212..509d628 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -1,5 +1,3 @@ -//go:build !integration - //go:generate oapi-codegen --config=oapi-codegen.yml openapi.yml package main diff --git a/cmd/web/main_integration.go b/cmd/web/main_integration.go deleted file mode 100644 index 94a2ef6..0000000 --- a/cmd/web/main_integration.go +++ /dev/null @@ -1,281 +0,0 @@ -//go:build integration - -package main - -import ( - "code.dlmw.ch/dlmw/qv/internal/migrations" - "code.dlmw.ch/dlmw/qv/internal/models" - "context" - "database/sql" - "errors" - "fmt" - . "github.com/Eun/go-hit" - "log/slog" - "net/http" - "os" - "strings" - "time" -) - -var addr = ":8080" - -func main() { - logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) - - err := os.Remove("./qv.integration.sqlite") - if err != nil { - logger.Error("couldn't delete qv.integration.sqlite") - } - db, err := openDb() - if err != nil { - logger.Error(err.Error()) - os.Exit(1) - } - defer db.Close() - err = migrations.Run(db) - if err != nil { - logger.Error(err.Error()) - os.Exit(1) - } - - app := &application{ - logger: logger, - elections: &models.ElectionModel{DB: db}, - voters: &models.VoterModel{DB: db}, - votes: &models.VoteModel{DB: db}, - } - - logger.Info("Starting integration tests server", "addr", addr) - - srv := &http.Server{ - Addr: addr, - Handler: app.routes(), - ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError), - IdleTimeout: time.Minute, - ReadTimeout: 5 * time.Second, - WriteTimeout: 10 * time.Second, - } - - go func() { - logger.Info("Starting integration test server", "addr", addr) - - if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { - logger.Error("Server error", "error", err) - os.Exit(1) - } - }() - - time.Sleep(1 * time.Second) // wait until srv starts - runTests() - - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - srv.Shutdown(ctx) -} - -func openDb() (*sql.DB, error) { - db, err := sql.Open("sqlite3", "./qv.integration.sqlite?_foreign_keys=on") - if err == nil { - err = db.Ping() - } - - return db, err -} - -func runTests() { - runCreateElectionTests() - runCreateVotesTests() -} - -func runCreateElectionTests() { - template := CombineSteps( - Post("http://127.0.0.1:8080/api/election"), - ) - - MustDo( - template, - Send().Body().String(` - { - "name": "Guy of the year", - "tokens": 100, - "areVotersKnown": false, - "maxVoters": 10, - "expiresAt": "2124-12-31T14:15:22Z", - "choices": [ - "Gandhi", - "Buddha" - ] -}`), - Expect().Status().Equal(http.StatusOK), - Expect().Headers("Location").Contains("/election/1"), - ) - - MustDo( - template, - Send().Body().String(` - { - "name": "Guy of the year", - "tokens": 100, - "areVotersKnown": false, - "maxVoters": 0, - "expiresAt": "2124-12-31T14:15:22Z", - "choices": [ - "Gandhi", - "Buddha" - ] -}`), - Expect().Status().Equal(http.StatusOK), - Expect().Headers("Location").Contains("/election/2"), - ) - - MustDo( - template, - Send().Body().String(` - { - "name": "Guy of the year", - "tokens": 100, - "areVotersKnown": true, - "maxVoters": 10, - "expiresAt": "2124-12-31T14:15:22Z", - "choices": [ - "Gandhi", - "Buddha" - ] -}`), - Expect().Status().Equal(http.StatusOK), - Expect().Headers("Location").Contains("/election/3"), - Expect().Body().JSON().JQ(".voterIdentities").Len().Equal(10), - ) - - MustDo( - template, - Send().Body().String(` -{ - "name": "Guy of the year", - "tokens": 100, - "areVotersKnown": true, - "maxVoters": -1, - "expiresAt": "2124-12-31T14:15:22Z", - "choices": [ - "Gandhi", - "Buddha" - ] -}`), - Expect().Status().Equal(http.StatusUnprocessableEntity), - ) - - MustDo( - template, - Send().Body().String(` -{ - "name": "Guy of the year", - "tokens": 100, - "areVotersKnown": false, - "maxVoters": -1, - "expiresAt": "2124-12-31T14:15:22Z", - "choices": [ - "Gandhi" - ] -}`), - Expect().Status().Equal(http.StatusUnprocessableEntity), - ) - - MustDo( - template, - Send().Body().String(` -{ - "name": "Guy of the year", - "tokens": 100, - "areVotersKnown": false, - "maxVoters": 10, - "expiresAt": "2018-12-31T14:15:22Z", - "choices": [ - "Gandhi", - "Buddha" - ] -}`), - Expect().Status().Equal(http.StatusUnprocessableEntity), - ) -} - -func runCreateVotesTests() { - uri := "http://127.0.0.1:8080/api/election/%v/votes" - - MustDo( - Post(fmt.Sprintf(uri, "1")), - Send().Body().String(` -{ - "choices": [ - { - "choiceText": "Buddha", - "tokens": 90 - }, - { - "choiceText": "Gandhi", - "tokens": 10 - } - ] -}`), - Expect().Status().Equal(http.StatusCreated), - ) - - MustDo( - Post(fmt.Sprintf(uri, "3")), - Send().Body().String(` -{ - "choices": [ - { - "choiceText": "Buddha", - "tokens": 90 - }, - { - "choiceText": "Gandhi", - "tokens": 10 - } - ] -}`), - Expect().Status().Equal(http.StatusUnprocessableEntity), - ) - - voterIdentities := make([]string, 0) - locationHeader := "" - MustDo( - Post("http://127.0.0.1:8080/api/election"), - Send().Body().String(` - { - "name": "Guy of the year", - "tokens": 100, - "areVotersKnown": true, - "maxVoters": 10, - "expiresAt": "2124-12-31T14:15:22Z", - "choices": [ - "Gandhi", - "Buddha" - ] -}`), - Expect().Status().Equal(http.StatusOK), - Store().Response().Body().JSON().JQ(".voterIdentities").In(&voterIdentities), - Store().Response().Headers("Location").In(&locationHeader), - ) - - electionId := strings.Split(locationHeader, "/")[2] - MustDo( - Post(fmt.Sprintf(uri, electionId)), - Send().Body().String(fmt.Sprintf(` -{ - "voterIdentity": "%v", - "choices": [ - { - "choiceText": "Buddha", - "tokens": 90 - }, - { - "choiceText": "Gandhi", - "tokens": 10 - } - ] -}`, voterIdentities[0])), - Expect().Status().Equal(http.StatusCreated), - ) -} diff --git a/cmd/web/openapi.yml b/cmd/web/openapi.yml index 4fd0bbd..4859e6f 100644 --- a/cmd/web/openapi.yml +++ b/cmd/web/openapi.yml @@ -34,7 +34,7 @@ paths: required: true description: The ID of the election schema: - type: integer + type: string responses: 200: description: Election returned @@ -102,7 +102,7 @@ paths: required: true description: The ID of the election schema: - type: integer + type: string requestBody: content: application/json: @@ -148,7 +148,7 @@ paths: required: true description: The ID of the election schema: - type: integer + type: string responses: 200: description: Election results returned @@ -190,7 +190,7 @@ components: - choices properties: id: - type: integer + type: string name: type: string tokens: diff --git a/cmd/web/testutils_test.go b/cmd/web/testutils_test.go index 9f86ccd..d27315f 100644 --- a/cmd/web/testutils_test.go +++ b/cmd/web/testutils_test.go @@ -65,12 +65,12 @@ type mockElectionModel struct { mock.Mock } -func (e *mockElectionModel) Insert(name string, tokens int, areVotersKnown bool, maxVoters int, choices []string, expiresAt time.Time) (int, error) { +func (e *mockElectionModel) Insert(name string, tokens int, areVotersKnown bool, maxVoters int, choices []string, expiresAt time.Time) (string, error) { args := e.Called(name, tokens, areVotersKnown, maxVoters, choices, expiresAt) - return args.Int(0), args.Error(1) + return args.String(0), args.Error(1) } -func (e *mockElectionModel) GetById(id int) (*models.Election, error) { +func (e *mockElectionModel) GetById(id string) (*models.Election, error) { args := e.Called(id) return args.Get(0).(*models.Election), args.Error(1) } @@ -79,17 +79,17 @@ type mockVoterModel struct { mock.Mock } -func (v *mockVoterModel) InsertMultiple(identities []string, electionID int) ([]int, error) { +func (v *mockVoterModel) InsertMultiple(identities []string, electionID string) ([]int, error) { args := v.Called(identities, electionID) return args.Get(0).([]int), args.Error(1) } -func (v *mockVoterModel) CountByElection(electionID int) (int, error) { +func (v *mockVoterModel) CountByElection(electionID string) (int, error) { args := v.Called(electionID) return args.Int(0), args.Error(1) } -func (v *mockVoterModel) Exists(voterIdentity string, electionID int) (bool, error) { +func (v *mockVoterModel) Exists(voterIdentity string, electionID string) (bool, error) { args := v.Called(voterIdentity, electionID) return args.Bool(0), args.Error(1) } @@ -98,17 +98,17 @@ type mockVoteModel struct { mock.Mock } -func (v *mockVoteModel) Insert(voterIdentity string, electionId int, choiceText string, tokens int) (int, error) { +func (v *mockVoteModel) Insert(voterIdentity string, electionId string, choiceText string, tokens int) (int, error) { args := v.Called(voterIdentity, electionId, choiceText, tokens) return args.Int(0), args.Error(1) } -func (v *mockVoteModel) Exists(voterIdentity string, electionID int) (bool, error) { +func (v *mockVoteModel) Exists(voterIdentity string, electionID string) (bool, error) { args := v.Called(voterIdentity, electionID) return args.Bool(0), args.Error(1) } -func (v *mockVoteModel) GetByElection(electionID int) (*[]models.Vote, error) { +func (v *mockVoteModel) GetByElection(electionID string) (*[]models.Vote, error) { args := v.Called(electionID) return args.Get(0).(*[]models.Vote), args.Error(1) } diff --git a/internal/generated.go b/internal/generated.go index 0b38806..de47ec9 100644 --- a/internal/generated.go +++ b/internal/generated.go @@ -47,7 +47,7 @@ type Election struct { Choices []string `json:"choices"` CreatedAt string `json:"createdAt"` ExpiresAt string `json:"expiresAt"` - Id int `json:"id"` + Id string `json:"id"` MaxVoters int `json:"maxVoters"` Name string `json:"name"` Tokens int `json:"tokens"` @@ -89,13 +89,13 @@ type ServerInterface interface { CreateElection(w http.ResponseWriter, r *http.Request) // Get an election // (GET /election/{id}) - GetElection(w http.ResponseWriter, r *http.Request, id int) + GetElection(w http.ResponseWriter, r *http.Request, id string) // Get the results of an election // (GET /election/{id}/results) - GetElectionResults(w http.ResponseWriter, r *http.Request, id int) + GetElectionResults(w http.ResponseWriter, r *http.Request, id string) // Cast your votes for an election // (POST /election/{id}/votes) - CreateVotes(w http.ResponseWriter, r *http.Request, id int) + CreateVotes(w http.ResponseWriter, r *http.Request, id string) } // ServerInterfaceWrapper converts contexts to parameters. @@ -129,7 +129,7 @@ func (siw *ServerInterfaceWrapper) GetElection(w http.ResponseWriter, r *http.Re var err error // ------------- Path parameter "id" ------------- - var id int + var id string err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { @@ -155,7 +155,7 @@ func (siw *ServerInterfaceWrapper) GetElectionResults(w http.ResponseWriter, r * var err error // ------------- Path parameter "id" ------------- - var id int + var id string err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { @@ -181,7 +181,7 @@ func (siw *ServerInterfaceWrapper) CreateVotes(w http.ResponseWriter, r *http.Re var err error // ------------- Path parameter "id" ------------- - var id int + var id string err = runtime.BindStyledParameterWithOptions("simple", "id", r.PathValue("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { diff --git a/internal/mappers/elections_test.go b/internal/mappers/elections_test.go index 52ff1d1..b7f176e 100644 --- a/internal/mappers/elections_test.go +++ b/internal/mappers/elections_test.go @@ -2,6 +2,7 @@ package mappers import ( "code.dlmw.ch/dlmw/qv/internal/models" + uuid2 "github.com/google/uuid" "github.com/stretchr/testify/assert" "testing" "time" @@ -19,8 +20,9 @@ func TestElectionResponse(t *testing.T) { t.Fatal(err.Error()) } + uuid, _ := uuid2.NewV7() election := models.Election{ - ID: 15, + ID: uuid.String(), Name: "The best", Tokens: 100, AreVotersKnown: true, @@ -32,7 +34,7 @@ func TestElectionResponse(t *testing.T) { response := ElectionResponse(&election) - assert.Equal(t, 15, response.Id) + assert.Equal(t, uuid.String(), response.Id) assert.Equal(t, "The best", response.Name) assert.Equal(t, 100, response.Tokens) assert.Equal(t, true, response.AreVotersKnown) diff --git a/internal/migrations/sql/000001_init.up.sql b/internal/migrations/sql/000001_init.up.sql index b9373d0..992278a 100644 --- a/internal/migrations/sql/000001_init.up.sql +++ b/internal/migrations/sql/000001_init.up.sql @@ -1,5 +1,5 @@ CREATE TABLE elections ( - id INTEGER PRIMARY KEY AUTOINCREMENT, -- TODO: try to generate a UUID + id TEXT PRIMARY KEY, name TEXT NOT NULL, tokens INTEGER NOT NULL, are_voters_known INTEGER NOT NULL, diff --git a/internal/models/elections.go b/internal/models/elections.go index 81aaef1..56e597f 100644 --- a/internal/models/elections.go +++ b/internal/models/elections.go @@ -2,12 +2,13 @@ package models import ( "database/sql" + "github.com/google/uuid" "time" ) type ElectionModelInterface interface { - Insert(name string, tokens int, areVotersKnown bool, maxVoters int, Choices []string, ExpiresAt time.Time) (int, error) - GetById(id int) (*Election, error) + Insert(name string, tokens int, areVotersKnown bool, maxVoters int, Choices []string, ExpiresAt time.Time) (string, error) + GetById(id string) (*Election, error) } type ElectionModel struct { @@ -15,7 +16,7 @@ type ElectionModel struct { } type Election struct { - ID int + ID string Name string Tokens int AreVotersKnown bool @@ -25,48 +26,48 @@ type Election struct { Choices []string } -func (e *ElectionModel) Insert(name string, tokens int, areVotersKnown bool, maxVoters int, choices []string, expiresAt time.Time) (int, error) { +func (e *ElectionModel) Insert(name string, tokens int, areVotersKnown bool, maxVoters int, choices []string, expiresAt time.Time) (string, error) { tx, err := e.DB.Begin() if err != nil { - return 0, err + return "", err } defer tx.Rollback() - result, err := tx.Exec(` - INSERT INTO elections (name, tokens, are_voters_known, max_voters, expires_at) - VALUES (?, ?, ?, ?, ?)`, name, tokens, areVotersKnown, maxVoters, expiresAt) + electionID, err := uuid.NewV7() if err != nil { - return 0, err + return "", err } - electionID, err := result.LastInsertId() + _, err = tx.Exec(` + INSERT INTO elections (id, name, tokens, are_voters_known, max_voters, expires_at) + VALUES (?, ?, ?, ?, ?, ?)`, electionID, name, tokens, areVotersKnown, maxVoters, expiresAt) if err != nil { - return 0, err + return "", err } stmt, err := tx.Prepare(` INSERT INTO choices (text, election_id) VALUES (?, ?)`) if err != nil { - return 0, err + return "", err } defer stmt.Close() for _, choice := range choices { _, err = stmt.Exec(choice, electionID) if err != nil { - return 0, err + return "", err } } if err = tx.Commit(); err != nil { - return 0, err + return "0", err } - return int(electionID), nil + return electionID.String(), nil } -func (e *ElectionModel) GetById(id int) (*Election, error) { +func (e *ElectionModel) GetById(id string) (*Election, error) { query := ` SELECT id, name, tokens, are_voters_known, max_voters, created_at, expires_at FROM elections diff --git a/internal/models/voters.go b/internal/models/voters.go index 6bb788b..08118a8 100644 --- a/internal/models/voters.go +++ b/internal/models/voters.go @@ -6,16 +6,16 @@ import ( ) type VoterModelInterface interface { - InsertMultiple(identities []string, electionID int) ([]int, error) - CountByElection(electionID int) (int, error) - Exists(voterIdentity string, electionID int) (bool, error) + InsertMultiple(identities []string, electionID string) ([]int, error) + CountByElection(electionID string) (int, error) + Exists(voterIdentity string, electionID string) (bool, error) } type VoterModel struct { DB *sql.DB } -func (v *VoterModel) InsertMultiple(identities []string, electionID int) ([]int, error) { +func (v *VoterModel) InsertMultiple(identities []string, electionID string) ([]int, error) { tx, err := v.DB.Begin() if err != nil { return nil, err @@ -53,7 +53,7 @@ func (v *VoterModel) InsertMultiple(identities []string, electionID int) ([]int, return voterIDs, nil } -func (v *VoterModel) CountByElection(electionID int) (int, error) { +func (v *VoterModel) CountByElection(electionID string) (int, error) { // use a transaction to prevent race conditions tx, err := v.DB.Begin() if err != nil { @@ -82,7 +82,7 @@ func (v *VoterModel) CountByElection(electionID int) (int, error) { return voterCount, nil } -func (v *VoterModel) Exists(voterIdentity string, electionID int) (bool, error) { +func (v *VoterModel) Exists(voterIdentity string, electionID string) (bool, error) { query := ` SELECT EXISTS ( SELECT 1 diff --git a/internal/models/votes.go b/internal/models/votes.go index a6e7ad0..330592b 100644 --- a/internal/models/votes.go +++ b/internal/models/votes.go @@ -7,9 +7,9 @@ import ( ) 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) + Insert(voterIdentity string, electionId string, choiceText string, tokens int) (int, error) + Exists(voterIdentity string, electionID string) (bool, error) + GetByElection(electionID string) (*[]Vote, error) } type VoteModel struct { @@ -18,13 +18,13 @@ type VoteModel struct { type Vote struct { VoterIdentity string - ElectionID int + ElectionID string ChoiceText string Tokens int CreatedAt time.Time } -func (v *VoteModel) Insert(voterIdentity string, electionId int, choiceText string, tokens int) (int, error) { +func (v *VoteModel) Insert(voterIdentity string, electionId string, choiceText string, tokens int) (int, error) { tx, err := v.DB.Begin() if err != nil { return 0, err @@ -47,7 +47,7 @@ func (v *VoteModel) Insert(voterIdentity string, electionId int, choiceText stri return int(voteId), nil } -func (v *VoteModel) Exists(voterIdentity string, electionID int) (bool, error) { +func (v *VoteModel) Exists(voterIdentity string, electionID string) (bool, error) { var exists bool query := ` SELECT EXISTS ( @@ -66,7 +66,7 @@ func (v *VoteModel) Exists(voterIdentity string, electionID int) (bool, error) { return exists, nil } -func (v *VoteModel) GetByElection(electionID int) (*[]Vote, error) { +func (v *VoteModel) GetByElection(electionID string) (*[]Vote, error) { query := ` SELECT voter_identity, election_id, choice_text, tokens, created_at FROM votes diff --git a/ui/create-election.html b/ui/create-election.html index b856b67..76bd409 100644 --- a/ui/create-election.html +++ b/ui/create-election.html @@ -93,7 +93,7 @@