Incomplete implementation of createVotes
This commit is contained in:
@ -3,7 +3,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
api "code.dlmw.ch/dlmw/qv/internal"
|
api "code.dlmw.ch/dlmw/qv/internal"
|
||||||
"code.dlmw.ch/dlmw/qv/internal/validator"
|
"code.dlmw.ch/dlmw/qv/internal/validator"
|
||||||
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -107,17 +109,16 @@ type createVotesRequestWithValidator struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *createVotesRequestWithValidator) isValid() bool {
|
func (r *createVotesRequestWithValidator) isValid() bool {
|
||||||
r.CheckField(validator.NotBlank(*r.VoterIdentity), "voterIdentity", "must not be blank")
|
r.CheckField(validator.GreaterThan(r.ElectionId, 0), "electionId", "must be greater than 0")
|
||||||
r.CheckField(validator.GreaterThan(*r.ElectionId, 0), "electionId", "must be greater than 0")
|
|
||||||
|
|
||||||
for _, choice := range *r.Choices {
|
for _, choice := range r.Choices {
|
||||||
r.CheckField(validator.NotBlank(*choice.ChoiceText), "choiceText", "must not be blank")
|
r.CheckField(validator.NotBlank(choice.ChoiceText), "choiceText", "must not be blank")
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.Valid()
|
return r.Valid()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *application) createVote(w http.ResponseWriter, r *http.Request) {
|
func (app *application) createVotes(w http.ResponseWriter, r *http.Request) {
|
||||||
var request createVotesRequestWithValidator
|
var request createVotesRequestWithValidator
|
||||||
|
|
||||||
if err := app.unmarshalRequest(r, &request); err != nil {
|
if err := app.unmarshalRequest(r, &request); err != nil {
|
||||||
@ -129,4 +130,32 @@ func (app *application) createVote(w http.ResponseWriter, r *http.Request) {
|
|||||||
app.unprocessableEntityError(w, request.Validator)
|
app.unprocessableEntityError(w, request.Validator)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
election, err := app.elections.GetById(request.ElectionId)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
app.unprocessableEntityErrorSingle(w, fmt.Errorf("election with id %v doesn't exist", request.ElectionId))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
app.serverError(w, r, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO check if he has voted
|
||||||
|
|
||||||
|
//var voterIdentity string
|
||||||
|
if election.AreVotersKnown {
|
||||||
|
if request.VoterIdentity == nil || validator.Blank(*request.VoterIdentity) {
|
||||||
|
app.unprocessableEntityErrorSingle(w, fmt.Errorf("election has known voters; you must provide an identity provided by the organizer"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//voterIdentity = *request.VoterIdentity
|
||||||
|
} else {
|
||||||
|
// TODO: get requester's IP address as identity
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO verify if choice exists
|
||||||
|
// TODO count tokens to make sure user isn't trying to cheat
|
||||||
|
|
||||||
|
json, _ := json.Marshal(election)
|
||||||
|
w.Write(json)
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,15 @@ func (app *application) unprocessableEntityError(w http.ResponseWriter, v valida
|
|||||||
json.NewEncoder(w).Encode(response)
|
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(),
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
func (app *application) unmarshalRequest(r *http.Request, dst any) error {
|
func (app *application) unmarshalRequest(r *http.Request, dst any) error {
|
||||||
body, err := io.ReadAll(r.Body)
|
body, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -221,15 +221,21 @@ components:
|
|||||||
|
|
||||||
CreateVotesRequest:
|
CreateVotesRequest:
|
||||||
type: object
|
type: object
|
||||||
|
required:
|
||||||
|
- electionId
|
||||||
|
- choices
|
||||||
properties:
|
properties:
|
||||||
voterIdentity:
|
voterIdentity:
|
||||||
type: string
|
type: string
|
||||||
minLength: 1
|
description: Must be filled if election has known voters
|
||||||
electionId:
|
electionId:
|
||||||
type: integer
|
type: integer
|
||||||
choices:
|
choices:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
|
required:
|
||||||
|
- choiceText
|
||||||
|
- tokens
|
||||||
properties:
|
properties:
|
||||||
choiceText:
|
choiceText:
|
||||||
type: string
|
type: string
|
||||||
|
@ -17,7 +17,7 @@ func (app *application) routes() http.Handler {
|
|||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
mux.HandleFunc("POST /election", app.createElection)
|
mux.HandleFunc("POST /election", app.createElection)
|
||||||
mux.HandleFunc("POST /vote", app.createVote)
|
mux.HandleFunc("POST /votes", app.createVotes)
|
||||||
|
|
||||||
standard := alice.New(app.recoverPanic, app.logRequest)
|
standard := alice.New(app.recoverPanic, app.logRequest)
|
||||||
return standard.Then(mux)
|
return standard.Then(mux)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"code.dlmw.ch/dlmw/qv/internal/models"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -48,6 +49,18 @@ func (e *mockElectionModel) Insert(name string, tokens int, areVotersKnown bool,
|
|||||||
return 1, nil
|
return 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *mockElectionModel) GetById(id int) (*models.Election, error) {
|
||||||
|
return &models.Election{
|
||||||
|
ID: id,
|
||||||
|
Name: "Guy of the year",
|
||||||
|
Tokens: 100,
|
||||||
|
AreVotersKnown: false,
|
||||||
|
MaxVoters: 10,
|
||||||
|
CreatedAt: time.Now().String(),
|
||||||
|
ExpiresAt: time.Now().Add(100 * time.Hour).String(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
type mockVoterModel struct {
|
type mockVoterModel struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,11 +30,13 @@ type CreateElectionResponse struct {
|
|||||||
|
|
||||||
// CreateVotesRequest defines model for CreateVotesRequest.
|
// CreateVotesRequest defines model for CreateVotesRequest.
|
||||||
type CreateVotesRequest struct {
|
type CreateVotesRequest struct {
|
||||||
Choices *[]struct {
|
Choices []struct {
|
||||||
ChoiceText *string `json:"choiceText,omitempty"`
|
ChoiceText string `json:"choiceText"`
|
||||||
Tokens *int `json:"tokens,omitempty"`
|
Tokens int `json:"tokens"`
|
||||||
} `json:"choices,omitempty"`
|
} `json:"choices"`
|
||||||
ElectionId *int `json:"electionId,omitempty"`
|
ElectionId int `json:"electionId"`
|
||||||
|
|
||||||
|
// VoterIdentity Must be filled if election has known voters
|
||||||
VoterIdentity *string `json:"voterIdentity,omitempty"`
|
VoterIdentity *string `json:"voterIdentity,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ CREATE TABLE elections (
|
|||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
tokens INTEGER NOT NULL,
|
tokens INTEGER NOT NULL,
|
||||||
are_voters_known INTEGER NOT NULL,
|
are_voters_known INTEGER NOT NULL,
|
||||||
max_voters INTEGER, -- mandatory when voters are known
|
max_voters INTEGER NOT NULL, -- must be greater than 0 when voters are known
|
||||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
expires_at DATETIME NOT NULL,
|
expires_at DATETIME NOT NULL,
|
||||||
CHECK (are_voters_known = 0 OR (are_voters_known = 1 AND max_voters IS NOT NULL AND max_voters >= 1))
|
CHECK (are_voters_known = 0 OR (are_voters_known = 1 AND max_voters IS NOT NULL AND max_voters >= 1))
|
||||||
|
@ -2,17 +2,30 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ElectionModelInterface interface {
|
type ElectionModelInterface interface {
|
||||||
Insert(name string, tokens int, areVotersKnown bool, maxVoters int, Choices []string, ExpiresAt time.Time) (int, error)
|
Insert(name string, tokens int, areVotersKnown bool, maxVoters int, Choices []string, ExpiresAt time.Time) (int, error)
|
||||||
|
GetById(id int) (*Election, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ElectionModel struct {
|
type ElectionModel struct {
|
||||||
DB *sql.DB
|
DB *sql.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Election struct {
|
||||||
|
ID int
|
||||||
|
Name string
|
||||||
|
Tokens int
|
||||||
|
AreVotersKnown bool
|
||||||
|
MaxVoters int
|
||||||
|
CreatedAt string
|
||||||
|
ExpiresAt string
|
||||||
|
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) (int, error) {
|
||||||
tx, err := e.DB.Begin()
|
tx, err := e.DB.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -53,3 +66,50 @@ func (e *ElectionModel) Insert(name string, tokens int, areVotersKnown bool, max
|
|||||||
|
|
||||||
return int(electionID), nil
|
return int(electionID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *ElectionModel) GetById(id int) (*Election, error) {
|
||||||
|
query := `
|
||||||
|
SELECT id, name, tokens, are_voters_known, max_voters, created_at, expires_at
|
||||||
|
FROM elections
|
||||||
|
WHERE id = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
row := e.DB.QueryRow(query, id)
|
||||||
|
|
||||||
|
election := &Election{}
|
||||||
|
var areVotersKnown int
|
||||||
|
|
||||||
|
err := row.Scan(&election.ID, &election.Name, &election.Tokens, &areVotersKnown, &election.MaxVoters, &election.CreatedAt, &election.ExpiresAt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
election.AreVotersKnown = areVotersKnown == 1
|
||||||
|
|
||||||
|
// Retrieve choices for the election
|
||||||
|
queryChoices := `
|
||||||
|
SELECT text
|
||||||
|
FROM choices
|
||||||
|
WHERE election_id = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
rows, err := e.DB.Query(queryChoices, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var choice string
|
||||||
|
if err := rows.Scan(&choice); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
election.Choices = append(election.Choices, choice)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("error iterating over choices for election ID %d: %v", id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return election, nil
|
||||||
|
}
|
||||||
|
@ -36,6 +36,10 @@ func (v *Validator) CheckField(ok bool, key, message string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Blank(value string) bool {
|
||||||
|
return strings.TrimSpace(value) == ""
|
||||||
|
}
|
||||||
|
|
||||||
func NotBlank(value string) bool {
|
func NotBlank(value string) bool {
|
||||||
return strings.TrimSpace(value) != ""
|
return strings.TrimSpace(value) != ""
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user