package main
import (
"database/sql"
"html/template"
"log"
"net/http"
"strings"
"github.com/gorilla/sessions"
_ "github.com/lib/pq"
)
/******************************************************************************
*
* Begin execution
*
******************************************************************************/
func main() {
initdb()
http.HandleFunc("/", handle_req)
log.Fatal(http.ListenAndServe(":8080", nil))
}
/******************************************************************************
*
* Global vars
*
******************************************************************************/
var db *sql.DB
/******************************************************************************
*
* Structs
*
******************************************************************************/
// Node records
type node struct {
id int
prompt string
return_node_id int
option_name string
options []string
}
// List node options as a string for presentation.
func (n node) optstr() string {
var opts = n.options
if n.return_node_id != 0 {
// The "return" option is available
opts = append(opts, "return")
}
var optstr = "Options: " + strings.Join(opts, ", ")
return optstr
}
// HTML template variables
type template_vars struct {
Intro string
Prompt string
Options string
}
/******************************************************************************
*
* Initialize database
*
******************************************************************************/
func initdb() {
var err error
// postgres_url is defined in secrets.go
db, err = sql.Open("postgres", postgres_url)
// Exit program if the database does not connect
if err != nil {
panic(err)
}
}
/******************************************************************************
*
* Handle HTTP request
*
******************************************************************************/
func handle_req(rw http.ResponseWriter, req *http.Request) {
// Access session values
var sess = load_session(req)
var currn = currn(sess)
// Access form values
var opt = form_values(req)
// Calculate next node
var nextn = nextn(currn, opt)
// Update session
set_currn(sess, nextn)
sess.Save(req, rw)
// Render HTML template
render_template(rw, nextn)
}
func load_session(req *http.Request) *sessions.Session {
// sessionKey is defined in secrets.go
var store = sessions.NewCookieStore([]byte(session_key))
var sess, _ = store.Get(req, "session")
return sess
}
func form_values(req *http.Request) string {
return req.FormValue("option")
}
/******************************************************************************
*
* Render HTML template
*
******************************************************************************/
func set_template_vars(nextn node) template_vars {
return template_vars{
Intro: "Hello, welcome to the Yo'el Mikha'el simulator.",
Prompt: nextn.prompt,
Options: nextn.optstr(),
}
}
func render_template(rw http.ResponseWriter, nextn node) {
var tmpl, _ = template.ParseFiles("index.html")
var tvars = set_template_vars(nextn)
tmpl.Execute(rw, tvars)
}
/******************************************************************************
*
* Access session data
*
******************************************************************************/
// Find current node from session
func currn(sess *sessions.Session) node {
var value, ok = sess.Values["current_node_id"]
var id int
if ok {
id = value.(int)
}
if id != 0 {
// A current node is found in the session.
return loadn(id)
} else {
// A current node has not been found in the session.
id = select_default_node_id()
sess.Values["current_node_id"] = id
return loadn(id)
}
}
// Set current node in session
func set_currn(sess *sessions.Session, nextn node) {
sess.Values["current_node_id"] = nextn.id
}
func select_default_node_id() int {
var row = db.QueryRow(
"SELECT id FROM nodes " +
"WHERE return_node_id IS NULL ORDER BY id LIMIT 1")
var id int
row.Scan(&id)
return id
}
/******************************************************************************
*
* Select records from the database
*
******************************************************************************/
func loadn(id int) node {
var n node
select_node_cols(&n, id)
select_node_options(&n, id)
return n
}
func select_node_cols(n *node, id int) {
var row = db.QueryRow(
"SELECT id, prompt, return_node_id, option_name "+
"FROM nodes WHERE id = $1", id)
row.Scan(&n.id, &n.prompt, &n.return_node_id, &n.option_name)
}
func select_node_options(n *node, id int) {
var rows, _ = db.Query(
"SELECT option_name FROM nodes "+
"WHERE return_node_id = $1", id)
var opt string
for rows.Next() {
rows.Scan(&opt)
n.options = append(n.options, opt)
}
}
/******************************************************************************
*
* Calculate next node based on selected option
*
******************************************************************************/
func nextn(currn node, opt string) node {
var id = select_nextn_id(currn, opt)
if id != 0 {
// A next node has been found.
return loadn(id)
}
if opt == "return" {
return handle_return_opt(currn)
}
// There is neither a next node, nor a return node.
return currn
}
func select_nextn_id(currn node, opt string) int {
// Look for next node based on return node and option name.
var row = db.QueryRow(
"SELECT id FROM nodes "+
"WHERE return_node_id = $1 AND option_name = $2",
currn.id, opt)
var id int
row.Scan(&id)
return id
}
func handle_return_opt(currn node) node {
if currn.return_node_id != 0 {
// A return node is set
return loadn(currn.return_node_id)
} else {
// No return node is set
return currn
}
}