package main import ( "database/sql" "encoding/json" "fmt" "html/template" "log" "net/http" "strconv" _ "github.com/mattn/go-sqlite3" ) const ( PORT = "8080" DB_FILE = "./datefinder.sqlite3" ) var ( db *sql.DB indexHtmlTemplate *template.Template ) type ChoicesPost struct { PollId int `json:"pollId"` Username string `json:"username"` Choices [][]bool `json:"choices"` } func initDatabase() { var err error db, err = sql.Open("sqlite3", DB_FILE) if err != nil { log.Fatalf("[!] Could not open database file %s, error: %s\n", DB_FILE, err) } const createPollsQuery = ` CREATE TABLE IF NOT EXISTS polls ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL ); ` _, err = db.Exec(createPollsQuery) if err != nil { log.Fatalf("[!] Could not create database table polls, error: %s\n", err) } const createChoicesQuery = ` CREATE TABLE IF NOT EXISTS choices ( id INTEGER PRIMARY KEY AUTOINCREMENT, pollid INTEGER NOT NULL, username TEXT NOT NULL, choices TEXT NOT NULL, FOREIGN KEY(pollid) REFERENCES polls(id) ); ` _, err = db.Exec(createChoicesQuery) if err != nil { log.Fatalf("[!] Could not create database table choices, error: %s\n", err) } } func prepareStaticContent() { var err error indexHtmlTemplate, err = template.ParseFiles("./static/index.html.tmpl") if err != nil { log.Fatalf("[!] Could not parse index.html template, error: %s\n", err) } } func handleGetRoot(w http.ResponseWriter, r *http.Request) { var pollName string pollId, err := strconv.Atoi(r.PathValue("pollId")) if err != nil { log.Printf("[!] Invallid poll id url segment, error: %s\n", err) http.Error(w, "Poll not found", http.StatusBadRequest) return } const query = ` SELECT name FROM polls WHERE id = ?; ` err = db.QueryRow(query, pollId).Scan(&pollName) if err != nil { log.Printf("[!] No poll with id %d was found, error: %s\n", pollId, err) http.Error(w, "Poll not found", http.StatusBadRequest) return } err = indexHtmlTemplate.Execute(w, struct { PollId int PollName string }{ PollId: pollId, PollName: pollName, }) if err != nil { log.Printf("[!] Could not execute index.html template, error: %s\n", err) http.Error(w, "Error rendering template", http.StatusInternalServerError) } } func handleGetPing(w http.ResponseWriter, r *http.Request) { log.Printf("[*] Received request %v %v\n", r.Method, r.URL) fmt.Fprintf(w, "PONG\n") } func handlePostSubmit(w http.ResponseWriter, r *http.Request) { log.Printf("[*] Received request %v %v\n", r.Method, r.URL) log.Printf("[*] %v\n", r.Body) w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET, POST") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Origin") var choicesPost ChoicesPost err := json.NewDecoder(r.Body).Decode(&choicesPost) if err != nil { log.Printf("[!] Error decoding request Body %v, error: %s\n", r.Body, err) w.WriteHeader(http.StatusBadRequest) return } choicesJson, err := json.Marshal(choicesPost.Choices) if err != nil { log.Printf("[!] Could not marshal choices, error: %s\n", err) } tx, err := db.Begin() if err != nil { log.Printf("[!] Could not begin database transaction, error: %s\n", err) w.WriteHeader(http.StatusBadRequest) return } const query = ` INSERT INTO choices (pollid, username, choices) VALUES (?, ?, ?); ` _, err = tx.Exec(query, choicesPost.PollId, choicesPost.Username, string(choicesJson)) if err != nil { log.Printf("[!] Could not insert choices into database, error: %s\n", err) tx.Rollback() w.WriteHeader(http.StatusBadRequest) return } tx.Commit() log.Printf("[*] Persisted choices for poll %d, username %s to database\n", choicesPost.PollId, choicesPost.Username) w.WriteHeader(http.StatusOK) } func handleOptionsSubmit(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET, POST") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Origin") w.WriteHeader(http.StatusNoContent) } func main() { initDatabase() defer db.Close() prepareStaticContent() http.HandleFunc( "GET /api/ping", handleGetPing, ) http.HandleFunc( "GET /favicon.ico", func(w http.ResponseWriter, r *http.Request) { http.Error(w, "There is no favicon.ico!", http.StatusBadRequest) }, ) http.HandleFunc( "GET /{pollId}", handleGetRoot, ) http.HandleFunc( "POST /api/submit", handlePostSubmit, ) http.HandleFunc( "OPTIONS /api/submit", handleOptionsSubmit, ) log.Printf("[*] Datefinder server listening on :%s\n", PORT) if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatalf("[!] Could not start server on %s, error: %s\n", PORT, err) } }