Insert election with uuid instead of auto-generated id

This commit is contained in:
2025-01-20 10:04:15 +01:00
parent 729fbecae6
commit 5668b1cd6a
13 changed files with 94 additions and 360 deletions

View File

@ -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) {

View File

@ -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,

View File

@ -1,5 +1,3 @@
//go:build !integration
//go:generate oapi-codegen --config=oapi-codegen.yml openapi.yml
package main

View File

@ -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),
)
}

View File

@ -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:

View File

@ -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)
}

View File

@ -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 {

View File

@ -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)

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -93,7 +93,7 @@
<template x-if="createdElectionId > 0">
<div class="mt-6 bg-green-100 p-4 rounded-md">
<h2 class="text-green-700 font-bold">Election Created Successfully</h2>
<p class="mt-2">Election ID: <span class="font-mono" x-text="createdElectionId"></span></p>
<p class="mt-2">Election ID: <span class="font-mono" x-text="createdElectionId"></span></p> <!-- TODO: link to created election -->
</div>
</template>