From 5e8b4e2b61d78fd1b40c55c1c4265e36a7fe3c7f Mon Sep 17 00:00:00 2001 From: paul Date: Tue, 4 Nov 2025 16:39:10 -0500 Subject: [PATCH] Adding initial code for adding a new poll on the backend --- go.mod | 2 +- server/config/config.go | 14 ++++++--- server/db/db.go | 49 +++++++++++++++++++++++--------- server/db/db_format.sql | 24 ---------------- server/db/db_test.go | 21 ++++++++------ server/models/poll.go | 2 +- server/services/poll.go | 41 ++++++++++++++++++++++++++ server/services/services_test.go | 45 +++++++++++++++++++++++++++++ 8 files changed, 147 insertions(+), 51 deletions(-) delete mode 100644 server/db/db_format.sql create mode 100644 server/services/poll.go create mode 100644 server/services/services_test.go diff --git a/go.mod b/go.mod index c5eda1c..6ec68ad 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module go-sjles-pta-vote/server +module go-sjles-pta-vote go 1.24.4 diff --git a/server/config/config.go b/server/config/config.go index 4621721..0c49a67 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -14,6 +14,7 @@ type Config struct { } var conf *Config +var conf_path string = ".env" func GetConfig() *Config { if conf != nil { @@ -23,7 +24,7 @@ func GetConfig() *Config { conf = &Config{} // TODO: Make this into a ini or toml file - confgContent, err := os.ReadFile(".env") + configContent, err := os.ReadFile(conf_path) if err != nil { fmt.Println("Error reading .env file: ", err) @@ -37,10 +38,15 @@ func GetConfig() *Config { for _, variable := range envVariables { if strings.Contains(variable, "=") { splitVariable := strings.Split(variable, "=") - envMap[splitVariable[0]] = splitVAriable[1] + envMap[splitVariable[0]] = splitVariable[1] } } + if err := json.Unmarshal([]byte(configContent), conf); err != nil { + fmt.Println("Error unmarshalling config file: ", err) + os.Exit(1) + } + // TODO: Better mapping of key to json values // TODO: Better error checking if values are missing // TODO: Default values @@ -59,6 +65,6 @@ func GetConfig() *Config { return conf } -func init() { - conf = GetConfig() +func SetConfig(init_conf *Config) { + conf = init_conf } \ No newline at end of file diff --git a/server/db/db.go b/server/db/db.go index 60c1ccc..aa4c1f3 100644 --- a/server/db/db.go +++ b/server/db/db.go @@ -2,30 +2,53 @@ package db import ( "database/sql" - "os" + + "go-sjles-pta-vote/server/config" _ "github.com/glebarez/go-sqlite" ) +var build_db_query string = ` +CREATE TABLE IF NOT EXISTS polls ( + id INTEGER PRIMARY KEY, + question TEXT NOT NULL, + member_yes_votes UNSIGNED INT NOT NULL DEFAULT 0, + member_no_votes UNSIGNED INT NOT NULL DEFAULT 0, + non_member_yes_votes UNSIGNED INT NOT NULL DEFAULT 0, + non_member_no_votes UNSIGNED INT NOT NULL DEFAULT 0, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + expires_at DATETIME +); + +CREATE TABLE IF NOT EXISTS voters ( + poll_id UNSIGNED INT NOT NULL, + voter_email TEXT NOT NULL, + FOREIGN KEY (poll_id) REFERENCES polls(id), + PRIMARY KEY (poll_id, voter_email) +); + +CREATE TABLE IF NOT EXISTS members ( + email TEXT NOT NULL, + member_name TEXT, + PRIMARY KEY (email) +); +` + var db *sql.DB -func Connect(db_path string) error { - var err error - db, err = sql.Open("sqlite", db_path) +func Connect() (*sql.DB, error) { + db_config := config.GetConfig() + + db, err := sql.Open("sqlite", db_config.DBPath) if err != nil { - return err + return nil, err } - sql_create, err := os.ReadFile("./db_format.sql") + _, err = db.Exec(build_db_query) - if err != nil { - return err - } - - _, err = db.Exec(string(sql_create)) - - return err + return db, err } func Close() { diff --git a/server/db/db_format.sql b/server/db/db_format.sql deleted file mode 100644 index 1721e07..0000000 --- a/server/db/db_format.sql +++ /dev/null @@ -1,24 +0,0 @@ -CREATE TABLE IF NOT EXISTS polls ( - id INTEGER PRIMARY KEY, - question TEXT NOT NULL, - member_yes_votes UNSIGNED INT NOT NULL, - member_no_votes UNSIGNED INT NOT NULL, - non_member_yes_votes UNSIGNED INT NOT NULL, - non_member_no_votes UNSIGNED INT NOT NULL, - created_at DATETIME, - updated_at DATETIME, - expires_at DATETIME -); - -CREATE TABLE IF NOT EXISTS voters ( - poll_id UNSIGNED INT NOT NULL, - voter_email TEXT NOT NULL, - FOREIGN KEY (poll_id) REFERENCES polls(id), - PRIMARY KEY (poll_id, voter_email) -); - -CREATE TABLE IF NOT EXISTS members ( - email TEXT NOT NULL, - member_name TEXT, - PRIMARY KEY (email) -); diff --git a/server/db/db_test.go b/server/db/db_test.go index ef57785..e4e32b0 100644 --- a/server/db/db_test.go +++ b/server/db/db_test.go @@ -3,21 +3,26 @@ package db import ( "os" "testing" + + "go-sjles-pta-vote/server/config" ) func TestConnect(t *testing.T) { tmp_db, err := os.CreateTemp("", "vote_test.*.db") - tmp_db_name := tmp_db.Name() - tmp_db.Close() - - defer os.Remove(tmp_db_name) - if err != nil { - t.Errorf(`Failed to create temporary db: %v`, err) + t.Errorf(`Failed to create temporary db file: %v`, err) } - if err := Connect(tmp_db_name); err != nil { - t.Errorf(`Failed to create the database at %s: %v`, tmp_db_name, err) + init_conf := &config.Config{ + DBPath: string(tmp_db.Name()), + } + config.SetConfig(init_conf) + + defer os.Remove(tmp_db.Name()) + tmp_db.Close() + + if _, err := Connect(); err != nil { + t.Errorf(`Failed to create the database: %v`, err) } defer Close() diff --git a/server/models/poll.go b/server/models/poll.go index 078c62e..fcc1505 100644 --- a/server/models/poll.go +++ b/server/models/poll.go @@ -1,7 +1,7 @@ package models type Poll struct { - ID int `json:"id"` + ID int64 `json:"id"` Question string `json:"question"` TotalVotes int `json:"total_votes"` WhoVoted []string `json:"who_voted"` diff --git a/server/services/poll.go b/server/services/poll.go new file mode 100644 index 0000000..56a4d8e --- /dev/null +++ b/server/services/poll.go @@ -0,0 +1,41 @@ +package services + +import ( + "go-sjles-pta-vote/server/db" + "go-sjles-pta-vote/server/models" +) + +func CreatePoll(poll *models.Poll) (*models.Poll, error) { + new_poll := models.Poll{} + + db_conn, err := db.Connect() + if err != nil { + return nil, err + } + defer db.Close() + + stmt, err := db_conn.Prepare(` + INSERT INTO polls ( + question, + expires_at + ) VALUES ( + $1, + $2 + ) RETURNING ID; + `) + + if err != nil { + return nil, err + } + + defer stmt.Close() + + res, err := stmt.Exec(poll.Question, poll.ExpiresAt) + if err != nil { + return nil, err + } + + new_poll.ID, err = res.LastInsertId() + + return &new_poll, err +} \ No newline at end of file diff --git a/server/services/services_test.go b/server/services/services_test.go new file mode 100644 index 0000000..a5d0a5d --- /dev/null +++ b/server/services/services_test.go @@ -0,0 +1,45 @@ +package services + +import ( + "os" + "testing" + "time" + + "go-sjles-pta-vote/server/config" + "go-sjles-pta-vote/server/db" + "go-sjles-pta-vote/server/models" +) + +func TestCreatePoll(t *testing.T) { + tmp_db, err := os.CreateTemp("", "vote_test.*.db") + if err != nil { + t.Errorf(`Failed to create temporary db file: %v`, err) + } + + init_conf := &config.Config{ + DBPath: string(tmp_db.Name()), + } + config.SetConfig(init_conf) + + defer os.Remove(tmp_db.Name()) + tmp_db.Close() + + if _, err := db.Connect(); err != nil { + t.Errorf(`Failed to create the database: %v`, err) + } + + create_poll := &models.Poll{ + Question: "TestQuestion", + ExpiresAt: time.Now().Add(time.Hour * 10).Format("2006-01-02 15:04:05"), + } + + new_poll, err := CreatePoll(create_poll) + + if err != nil { + t.Errorf(`Failed to insert into table: %v`, err) + } + + if new_poll.ID != 1 { + t.Errorf(`Failed to insert into table: Index %d: error %v`, new_poll.ID, err) + } +} \ No newline at end of file