package services import ( "database/sql" "errors" "time" "go-sjles-pta-vote/server/db" "go-sjles-pta-vote/server/models" ) var ErrQuestionAlreadyExists = errors.New("Question already exists") var ErrQuestionDoesntExist = errors.New("Question does not exist yet") var ErrVoterAlreadyVoted = errors.New("Voter already voted") 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() get_stmt, err := db_conn.Prepare(` SELECT id FROM polls WHERE question == $1 `) if err != nil { return nil, err } defer get_stmt.Close() var id int err = get_stmt.QueryRow(poll.Question).Scan(&id) if err != sql.ErrNoRows { if err != nil { return nil, err } return nil, ErrQuestionAlreadyExists } 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 } func GetPollByQuestion(question string) (*models.Poll, error) { new_poll := models.Poll{} db_conn, err := db.Connect() if err != nil { return nil, err } defer db.Close() get_poll_stmt, err := db_conn.Prepare(` SELECT id, question, member_yes_votes, member_no_votes, non_member_yes_votes, non_member_no_votes, created_at, updated_at, expires_at FROM polls WHERE question == $1 `) if err != nil { return nil, err } defer get_poll_stmt.Close() err = get_poll_stmt.QueryRow(question).Scan( &new_poll.ID, &new_poll.Question, &new_poll.MemberYes, &new_poll.MemberNo, &new_poll.NonMemberYes, &new_poll.NonMemberNo, &new_poll.CreatedAt, &new_poll.UpdatedAt, &new_poll.ExpiresAt, ) if err == sql.ErrNoRows { return nil, ErrQuestionDoesntExist } else if err != nil { return nil, err } get_voters_stmt, err := db_conn.Prepare (` SELECT voter_email FROM voters WHERE poll_id == $1 `) if err != nil { return nil, err } defer get_voters_stmt.Close() rows, err := get_voters_stmt.Query(new_poll.ID) for rows.Next() { var voter_email string err = rows.Scan(&voter_email) if err != nil { return nil, err } new_poll.WhoVoted = append(new_poll.WhoVoted, voter_email) } return &new_poll, nil } func GetAndCreatePollByQuestion(question string) (*models.Poll, error) { new_poll, err := GetPollByQuestion(question) if err == ErrQuestionDoesntExist { create_poll := &models.Poll{ Question: question, ExpiresAt: time.Now().Add(time.Hour * 10).Format("2006-01-02 15:04:05"), } if _, err = CreatePoll(create_poll); err != nil { return nil, err } return GetPollByQuestion(question) } else if err != nil { return nil, err } else { return new_poll, err } } func SetVote(poll_id int64, email string, vote bool) error { db_conn, err := db.Connect() if err != nil { return err } defer db.Close() set_voter_stmt, err := db_conn.Prepare(` INSERT IGNORE INTO voters (poll_id, voter_email) VALUES ($1, $2) `) if err != nil { return err } defer set_voter_stmt.Close() res, err := set_voter_stmt.Exec(poll_id, email) if err != nil { return err } else { rows_changed, err := res.RowsAffected() if rows_changed != 1 { return ErrVoterAlreadyVoted } else if err != nil { return err } } is_voter_member_stmt, err := db_conn.Prepare(` SELECT 1 FROM members WHERE email == $1 `) if err != nil { return err } defer is_voter_member_stmt.Close() var member_check int64 is_member := true err = is_voter_member_stmt.QueryRow(email).Scan(&member_check) if err == sql.ErrNoRows { is_member = false } else if err != nil { return err } // Member column name is not dependant on user input // So it's ok to put it directly in the query member_column_name := "member_" if !is_member { member_column_name = "non_" + member_column_name } if vote { member_column_name += "yes_votes" } else { member_column_name += "no_votes" } add_vote_stmt, err := db_conn.Prepare(` UPDATE polls SET ` + member_column_name + ` = ` + member_column_name + ` 1 WHERE id == $1 `) if err != nil { return err } defer add_vote_stmt.Close() res, err = add_vote_stmt.Exec(poll_id) if err != nil { return err } if num, err := res.RowsAffected(); num != 1 { return errors.New("Failed to update votes") } else if err != nil { return err } return nil }