Stash changes to switch computers
This commit is contained in:
parent
ddac93a550
commit
9cee572955
11
.gitignore
vendored
11
.gitignore
vendored
@ -1,2 +1,13 @@
|
||||
# Don't commit git
|
||||
.git
|
||||
|
||||
# Remove database
|
||||
formdata.db
|
||||
|
||||
# tmp files
|
||||
output.txt
|
||||
typer.py
|
||||
README.md
|
||||
go.mod
|
||||
go.sum
|
||||
demo.html
|
@ -10,23 +10,26 @@
|
||||
<div class="container">
|
||||
<h1>Submitted Answers</h1>
|
||||
<button id="dark-mode-toggle">Toggle Dark Mode</button>
|
||||
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Question</th>
|
||||
<th>Answer</th>
|
||||
<th>Tags</th>
|
||||
</tr>
|
||||
{{range .}}
|
||||
<tr>
|
||||
<td>{{.QuestionText}}</td>
|
||||
<td>{{.Answer}}</td>
|
||||
<td>{{.Tags}}</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr>
|
||||
<td colspan="2">No submissions found.</td>
|
||||
<td colspan="3">No submissions found.</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/static/theme.js"></script>
|
||||
|
44
index.html
44
index.html
@ -3,23 +3,47 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Form</title>
|
||||
<title>Find Classmates</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Submit Your Information</h1>
|
||||
<button id="dark-mode-toggle">Toggle Dark Mode</button>
|
||||
<form action="/submit" method="post">
|
||||
{{range .}}
|
||||
<label>{{.QuestionText}}:</label>
|
||||
<input type="{{.QuestionType}}" name="custom_{{.ID}}" required><br><br>
|
||||
{{end}}
|
||||
<div class="content">
|
||||
<div class="form-section">
|
||||
<h1>Enter Your Information</h1>
|
||||
<button id="dark-mode-toggle">Toggle Dark Mode</button>
|
||||
<form action="/submit" method="post">
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" id="name" name="name" required><br><br>
|
||||
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
<label for="email">Email:</label>
|
||||
<input type="email" id="email" name="email"><br><br>
|
||||
|
||||
<label for="phone">Phone:</label>
|
||||
<input type="text" id="phone" name="phone"><br><br>
|
||||
|
||||
<label for="dorm">Dorm:</label>
|
||||
<input type="text" id="dorm" name="dorm"><br><br>
|
||||
|
||||
<label>Classes:</label>
|
||||
<div class="tags-input-container" id="classes-container">
|
||||
<input type="text" id="classes-input" name="classes" placeholder="Enter classes" />
|
||||
</div><br><br>
|
||||
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="matches-section">
|
||||
<h2>People with Similar Classes</h2>
|
||||
<div id="matches-container">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/theme.js"></script>
|
||||
<script src="/static/util.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
450
main.go
450
main.go
@ -2,10 +2,12 @@ package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
@ -14,225 +16,351 @@ type Question struct {
|
||||
ID int
|
||||
QuestionText string
|
||||
QuestionType string
|
||||
Options []string
|
||||
}
|
||||
|
||||
type Match struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Classes []string `json:"classes"`
|
||||
}
|
||||
|
||||
var db *sql.DB
|
||||
|
||||
func renderForm(w http.ResponseWriter, r *http.Request) {
|
||||
rows, err := db.Query("SELECT id, question_text, question_type FROM questions ORDER BY question_order")
|
||||
if err != nil {
|
||||
log.Printf("Error fetching questions: %v\n", err)
|
||||
http.Error(w, "Failed to fetch form questions", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var questions []Question
|
||||
|
||||
for rows.Next() {
|
||||
var question Question
|
||||
err := rows.Scan(&question.ID, &question.QuestionText, &question.QuestionType)
|
||||
if err != nil {
|
||||
log.Printf("Error scanning question: %v\n", err)
|
||||
continue
|
||||
}
|
||||
questions = append(questions, question)
|
||||
}
|
||||
|
||||
log.Println("[DEBUG] Entering renderForm handler")
|
||||
tmpl := template.Must(template.ParseFiles("index.html"))
|
||||
tmpl.Execute(w, questions)
|
||||
log.Println("[DEBUG] Rendering template with questions")
|
||||
err := tmpl.Execute(w, nil)
|
||||
if err != nil {
|
||||
log.Println("[ERROR] Error rendering template:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func handleFormSubmit(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
|
||||
log.Println("[DEBUG] Entering handleFormSubmit handler")
|
||||
|
||||
// Parse the form data
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "Invalid form data", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
log.Printf("Error parsing form: %v\n", err)
|
||||
http.Error(w, "Failed to parse form", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
insertAnswerQuery := `INSERT INTO answers (question_id, answer) VALUES (?, ?)`
|
||||
name := r.FormValue("name")
|
||||
email := r.FormValue("email")
|
||||
phone := r.FormValue("phone")
|
||||
dorm := r.FormValue("dorm")
|
||||
classes := r.FormValue("classes")
|
||||
|
||||
// Insert the user data into the database
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
log.Printf("Error starting transaction: %v\n", err)
|
||||
http.Error(w, "Failed to save answers", http.StatusInternalServerError)
|
||||
log.Fatalf("[ERROR] Failed to begin transaction: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
|
||||
result, err := tx.Exec(`INSERT INTO users (name, email, phone, dorm) VALUES (?, ?, ?, ?)`, name, email, phone, dorm)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
log.Fatalf("[ERROR] Failed to insert user: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
log.Fatalf("[ERROR] Failed to get last insert ID: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Insert the classes associated with this user
|
||||
classList := strings.Split(classes, ",")
|
||||
for _, class := range classList {
|
||||
_, err := tx.Exec(`INSERT INTO classes (user_id, class_name) VALUES (?, ?)`, userID, strings.TrimSpace(class))
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
} else {
|
||||
tx.Commit()
|
||||
}
|
||||
}()
|
||||
|
||||
for key, values := range r.Form {
|
||||
if len(key) > 7 && key[:7] == "custom_" {
|
||||
var questionID int
|
||||
_, err := fmt.Sscanf(key, "custom_%d", &questionID)
|
||||
if err != nil {
|
||||
log.Printf("Error parsing question ID from field name %s: %v\n", key, err)
|
||||
continue
|
||||
}
|
||||
|
||||
answer := values[0]
|
||||
_, err = tx.Exec(insertAnswerQuery, questionID, answer)
|
||||
if err != nil {
|
||||
log.Printf("Error saving answer for question %d: %v\n", questionID, err)
|
||||
http.Error(w, "Failed to save answers", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
log.Fatalf("[ERROR] Failed to insert class: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
log.Fatalf("[ERROR] Failed to commit transaction: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("[DEBUG] User and classes inserted successfully")
|
||||
fmt.Fprintf(w, "Thank you for your submission!")
|
||||
log.Println("[DEBUG] Exiting handleFormSubmit handler")
|
||||
}
|
||||
|
||||
func handleAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
rows, err := db.Query(`
|
||||
SELECT q.question_text, a.answer
|
||||
FROM answers a
|
||||
JOIN questions q ON a.question_id = q.id
|
||||
ORDER BY q.question_order
|
||||
`)
|
||||
// func handleAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
// log.Println("[DEBUG] Entering handleAdmin handler")
|
||||
// rows, err := db.Query(`
|
||||
// SELECT q.question_text, a.answer, GROUP_CONCAT(t.tag, ', ') as tags
|
||||
// FROM answers a
|
||||
// JOIN questions q ON a.question_id = q.id
|
||||
// LEFT JOIN tags t ON a.id = t.answer_id
|
||||
// GROUP BY a.id
|
||||
// ORDER BY q.question_order
|
||||
// `)
|
||||
// if err != nil {
|
||||
// log.Printf("[ERROR] Error fetching submissions: %v\n", err)
|
||||
// http.Error(w, "Failed to fetch submissions", http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
// defer rows.Close()
|
||||
//
|
||||
// var submissions []struct {
|
||||
// QuestionText string
|
||||
// Answer string
|
||||
// Tags string
|
||||
// }
|
||||
// log.Println("[DEBUG] Fetching submissions")
|
||||
//
|
||||
// for rows.Next() {
|
||||
// var submission struct {
|
||||
// QuestionText string
|
||||
// Answer string
|
||||
// Tags string
|
||||
// }
|
||||
// err := rows.Scan(&submission.QuestionText, &submission.Answer, &submission.Tags)
|
||||
// if err != nil {
|
||||
// log.Printf("[ERROR] Error scanning submission: %v\n", err)
|
||||
// continue
|
||||
// }
|
||||
// submissions = append(submissions, submission)
|
||||
// log.Printf("[DEBUG] Fetched submission: %v\n", submission)
|
||||
// }
|
||||
//
|
||||
// tmpl := template.Must(template.ParseFiles("admin.html"))
|
||||
// log.Println("[DEBUG] Rendering admin template with submissions")
|
||||
// tmpl.Execute(w, submissions)
|
||||
// }
|
||||
//
|
||||
// func handleAddQuestion(w http.ResponseWriter, r *http.Request) {
|
||||
// log.Println("[DEBUG] Entering handleAddQuestion handler")
|
||||
// if r.Method != http.MethodPost {
|
||||
// log.Printf("[ERROR] Invalid request method: %v\n", r.Method)
|
||||
// http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// r.ParseForm()
|
||||
// questionText := r.FormValue("question_text")
|
||||
// questionType := r.FormValue("question_type")
|
||||
// options := r.FormValue("options")
|
||||
//
|
||||
// log.Printf("[DEBUG] Adding new question: %s, Type: %s\n", questionText, questionType)
|
||||
//
|
||||
// insertQuery := `INSERT INTO questions (question_text, question_type, question_order) VALUES (?, ?, (SELECT IFNULL(MAX(question_order), 0) + 1 FROM questions));`
|
||||
// res, err := db.Exec(insertQuery, questionText, questionType)
|
||||
// if err != nil {
|
||||
// log.Printf("[ERROR] Error adding new question: %v\n", err)
|
||||
// http.Error(w, "Failed to add question", http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// if questionType == "multiple_choice" || questionType == "single_choice" || questionType == "dropdown" || questionType == "tags" {
|
||||
// questionID, err := res.LastInsertId()
|
||||
// if err != nil {
|
||||
// log.Printf("[ERROR] Error getting question ID: %v\n", err)
|
||||
// http.Error(w, "Failed to add question", http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
// log.Printf("[DEBUG] Inserted question ID: %d\n", questionID)
|
||||
//
|
||||
// optionsList := strings.Split(options, ",")
|
||||
// for _, option := range optionsList {
|
||||
// option = strings.TrimSpace(option)
|
||||
// log.Printf("[DEBUG] Adding option: %s to question ID: %d\n", option, questionID)
|
||||
// _, err = db.Exec("INSERT INTO question_options (question_id, option_text) VALUES (?, ?)", questionID, option)
|
||||
// if err != nil {
|
||||
// log.Printf("[ERROR] Error adding options: %v\n", err)
|
||||
// http.Error(w, "Failed to add options", http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// log.Println("[DEBUG] Redirecting to manage page")
|
||||
// http.Redirect(w, r, "/manage", http.StatusSeeOther)
|
||||
// }
|
||||
//
|
||||
// func handleManage(w http.ResponseWriter, r *http.Request) {
|
||||
// log.Println("[DEBUG] Entering handleManage handler")
|
||||
// rows, err := db.Query("SELECT id, question_text, question_type FROM questions ORDER BY question_order")
|
||||
// if err != nil {
|
||||
// log.Printf("[ERROR] Error fetching questions: %v\n", err)
|
||||
// http.Error(w, "Failed to fetch questions", http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
// defer rows.Close()
|
||||
//
|
||||
// var questions []Question
|
||||
// log.Println("[DEBUG] Fetching questions for management")
|
||||
//
|
||||
// for rows.Next() {
|
||||
// var question Question
|
||||
// err := rows.Scan(&question.ID, &question.QuestionText, &question.QuestionType)
|
||||
// if err != nil {
|
||||
// log.Printf("[ERROR] Error scanning question: %v\n", err)
|
||||
// continue
|
||||
// }
|
||||
// log.Printf("[DEBUG] Found question: %v\n", question)
|
||||
//
|
||||
// if question.QuestionType == "multiple_choice" || question.QuestionType == "single_choice" || question.QuestionType == "dropdown" {
|
||||
// optionRows, err := db.Query("SELECT option_text FROM question_options WHERE question_id = ?", question.ID)
|
||||
// if err != nil {
|
||||
// log.Printf("[ERROR] Error fetching options: %v\n", err)
|
||||
// continue
|
||||
// }
|
||||
// defer optionRows.Close()
|
||||
//
|
||||
// for optionRows.Next() {
|
||||
// var optionText string
|
||||
// err = optionRows.Scan(&optionText)
|
||||
// if err != nil {
|
||||
// log.Printf("[ERROR] Error scanning option: %v\n", err)
|
||||
// continue
|
||||
// }
|
||||
// question.Options = append(question.Options, optionText)
|
||||
// log.Printf("[DEBUG] Found option: %s\n", optionText)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// questions = append(questions, question)
|
||||
// }
|
||||
//
|
||||
// tmpl := template.Must(template.ParseFiles("manage.html"))
|
||||
// log.Println("[DEBUG] Rendering manage template with questions")
|
||||
// tmpl.Execute(w, questions)
|
||||
// }
|
||||
//
|
||||
// func handleRemoveQuestion(w http.ResponseWriter, r *http.Request) {
|
||||
// log.Println("[DEBUG] Entering handleRemoveQuestion handler")
|
||||
// questionID := r.URL.Query().Get("id")
|
||||
// log.Printf("[DEBUG] Removing question with ID: %s\n", questionID)
|
||||
//
|
||||
// deleteQuery := `DELETE FROM questions WHERE id = ?`
|
||||
// _, err := db.Exec(deleteQuery, questionID)
|
||||
// if err != nil {
|
||||
// log.Printf("[ERROR] Error removing question: %v\n", err)
|
||||
// http.Error(w, "Failed to remove question", http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// log.Println("[DEBUG] Redirecting to manage page after deletion")
|
||||
// http.Redirect(w, r, "/manage", http.StatusSeeOther)
|
||||
// }
|
||||
|
||||
func handleFetchMatches(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("[DEBUG] Entering handleFetchMatches handler")
|
||||
|
||||
// Parse the request body
|
||||
var requestData struct {
|
||||
Classes []string `json:"classes"`
|
||||
}
|
||||
err := json.NewDecoder(r.Body).Decode(&requestData)
|
||||
if err != nil {
|
||||
log.Printf("Error fetching submissions: %v\n", err)
|
||||
http.Error(w, "Failed to fetch submissions", http.StatusInternalServerError)
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
classes := requestData.Classes
|
||||
|
||||
// Prepare the SQL query to find matching users based on classes
|
||||
query := `
|
||||
SELECT u.name, u.email, GROUP_CONCAT(c.class_name, ', ') AS classes
|
||||
FROM users u
|
||||
JOIN classes c ON u.id = c.user_id
|
||||
WHERE c.class_name IN (` + strings.Repeat("?,", len(classes)-1) + `?)
|
||||
GROUP BY u.id
|
||||
`
|
||||
|
||||
args := make([]interface{}, len(classes))
|
||||
for i, class := range classes {
|
||||
args[i] = class
|
||||
}
|
||||
|
||||
rows, err := db.Query(query, args...)
|
||||
if err != nil {
|
||||
http.Error(w, "Database query failed", http.StatusInternalServerError)
|
||||
log.Fatalf("[ERROR] Query failed: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var submissions []struct {
|
||||
QuestionText string
|
||||
Answer string
|
||||
}
|
||||
|
||||
var people []map[string]interface{}
|
||||
for rows.Next() {
|
||||
var submission struct {
|
||||
QuestionText string
|
||||
Answer string
|
||||
var name, email, classList string
|
||||
if err := rows.Scan(&name, &email, &classList); err != nil {
|
||||
log.Fatalf("[ERROR] Row scan failed: %v\n", err)
|
||||
return
|
||||
}
|
||||
err := rows.Scan(&submission.QuestionText, &submission.Answer)
|
||||
if err != nil {
|
||||
log.Printf("Error scanning submission: %v\n", err)
|
||||
continue
|
||||
|
||||
person := map[string]interface{}{
|
||||
"name": name,
|
||||
"email": email,
|
||||
"classes": strings.Split(classList, ", "),
|
||||
}
|
||||
submissions = append(submissions, submission)
|
||||
people = append(people, person)
|
||||
}
|
||||
|
||||
tmpl := template.Must(template.ParseFiles("admin.html"))
|
||||
tmpl.Execute(w, submissions)
|
||||
}
|
||||
// Send the matching people as JSON response
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(people)
|
||||
|
||||
func handleAddQuestion(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
r.ParseForm()
|
||||
questionText := r.FormValue("question_text")
|
||||
|
||||
insertQuery := `INSERT INTO questions (question_text, question_type, question_order) VALUES (?, 'text', (SELECT IFNULL(MAX(question_order), 0) + 1 FROM questions));`
|
||||
_, err := db.Exec(insertQuery, questionText)
|
||||
if err != nil {
|
||||
log.Printf("Error adding new question: %v\n", err)
|
||||
http.Error(w, "Failed to add question", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/manage", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func handleManage(w http.ResponseWriter, r *http.Request) {
|
||||
rows, err := db.Query("SELECT id, question_text, question_type FROM questions ORDER BY question_order")
|
||||
if err != nil {
|
||||
log.Printf("Error fetching questions: %v\n", err)
|
||||
http.Error(w, "Failed to fetch questions", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var questions []Question
|
||||
|
||||
for rows.Next() {
|
||||
var question Question
|
||||
err := rows.Scan(&question.ID, &question.QuestionText, &question.QuestionType)
|
||||
if err != nil {
|
||||
log.Printf("Error scanning question: %v\n", err)
|
||||
continue
|
||||
}
|
||||
questions = append(questions, question)
|
||||
}
|
||||
|
||||
if len(questions) == 0 {
|
||||
log.Println("No questions found")
|
||||
}
|
||||
|
||||
tmpl := template.Must(template.ParseFiles("manage.html"))
|
||||
err = tmpl.Execute(w, questions)
|
||||
if err != nil {
|
||||
log.Printf("Error executing template: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func handleRemoveQuestion(w http.ResponseWriter, r *http.Request) {
|
||||
questionID := r.URL.Query().Get("id")
|
||||
|
||||
deleteQuery := `DELETE FROM questions WHERE id = ?`
|
||||
_, err := db.Exec(deleteQuery, questionID)
|
||||
if err != nil {
|
||||
log.Printf("Error removing question: %v\n", err)
|
||||
http.Error(w, "Failed to remove question", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/manage", http.StatusSeeOther)
|
||||
log.Println("[DEBUG] Exiting handleFetchMatches handler")
|
||||
}
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
|
||||
log.Println("[DEBUG] Opening database connection")
|
||||
db, err = sql.Open("sqlite3", "./formdata.db")
|
||||
// db, err = sql.Open("sqlite3", ":memory:")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
createTableQueries := `
|
||||
CREATE TABLE IF NOT EXISTS questions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
question_text TEXT NOT NULL,
|
||||
question_type TEXT NOT NULL DEFAULT 'text',
|
||||
question_order INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT,
|
||||
phone TEXT,
|
||||
dorm TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS answers (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
question_id INTEGER NOT NULL,
|
||||
answer TEXT NOT NULL,
|
||||
FOREIGN KEY (question_id) REFERENCES questions(id)
|
||||
);
|
||||
`
|
||||
CREATE TABLE IF NOT EXISTS classes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER,
|
||||
class_name TEXT NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
`
|
||||
|
||||
log.Println("[DEBUG] Creating database tables")
|
||||
_, err = db.Exec(createTableQueries)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Fatalf("[ERROR] Error creating tables: %v\n", err)
|
||||
return
|
||||
}
|
||||
log.Println("[DEBUG] Database tables created successfully")
|
||||
|
||||
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
||||
|
||||
http.HandleFunc("/", renderForm)
|
||||
http.HandleFunc("/submit", handleFormSubmit)
|
||||
http.HandleFunc("/admin", handleAdmin)
|
||||
http.HandleFunc("/manage", handleManage)
|
||||
http.HandleFunc("/manage/add", handleAddQuestion)
|
||||
http.HandleFunc("/manage/remove", handleRemoveQuestion)
|
||||
// http.HandleFunc("/admin", handleAdmin)
|
||||
// http.HandleFunc("/manage", handleManage)
|
||||
// http.HandleFunc("/manage/add", handleAddQuestion)
|
||||
// http.HandleFunc("/manage/remove", handleRemoveQuestion)
|
||||
http.HandleFunc("/fetch-matches", handleFetchMatches)
|
||||
|
||||
log.Println("[DEBUG] Server starting at :8080")
|
||||
fmt.Println("Server started at :8080")
|
||||
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
|
32
manage.html
32
manage.html
@ -10,35 +10,63 @@
|
||||
<div class="container">
|
||||
<h1>Manage Questions</h1>
|
||||
|
||||
<!-- Dark mode toggle button -->
|
||||
<button id="dark-mode-toggle">Toggle Dark Mode</button>
|
||||
|
||||
<h2>Add New Question</h2>
|
||||
<form action="/manage/add" method="post">
|
||||
<label for="question_text">Question Text:</label>
|
||||
<input type="text" id="question_text" name="question_text" required><br><br>
|
||||
|
||||
<label for="question_type">Question Type:</label>
|
||||
<select id="question_type" name="question_type" required>
|
||||
<option value="text">Text</option>
|
||||
<option value="multiple_choice">Multiple Choice</option>
|
||||
<option value="single_choice">Single Choice</option>
|
||||
<option value="dropdown">Dropdown</option>
|
||||
<option value="tags">Tag Input</option>
|
||||
</select><br><br>
|
||||
|
||||
<div id="options-section" style="display:none;">
|
||||
<label for="options">Options (comma-separated):</label>
|
||||
<textarea id="options" name="options" rows="4" cols="50"></textarea><br><br>
|
||||
</div>
|
||||
|
||||
<input type="submit" value="Add Question">
|
||||
</form>
|
||||
|
||||
|
||||
<h2>Existing Questions</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Question Text</th>
|
||||
<th>Question Type</th>
|
||||
<th>Options</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
{{range .}}
|
||||
<tr>
|
||||
<td>{{.QuestionText}}</td>
|
||||
<td>{{.QuestionType}}</td>
|
||||
<td>
|
||||
{{if .Options}}
|
||||
<ul>
|
||||
{{range .Options}}
|
||||
<li>{{.}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{else}}None{{end}}
|
||||
</td>
|
||||
<td><a href="/manage/remove?id={{.ID}}">Remove</a></td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr>
|
||||
<td colspan="2">No questions found.</td>
|
||||
<td colspan="4">No questions found.</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script src="/static/theme.js"></script>
|
||||
<script src="/static/util.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -145,3 +145,73 @@ a.dark-mode:hover {
|
||||
color: #3399ff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.form-section, .matches-section {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.form-section {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.matches-section {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.form-section, .matches-section {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.tags-input-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
border: 1px solid #ccc;
|
||||
padding: 8px;
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.tags-input-container:focus-within {
|
||||
border-color: #0073e6;
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 3px;
|
||||
padding: 4px 8px;
|
||||
margin: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.tag:hover .close {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tag .close {
|
||||
margin-left: 8px;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
font-size: 14px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.tags-input-container input {
|
||||
border: none;
|
||||
outline: none;
|
||||
flex-grow: 1;
|
||||
font-size: 14px;
|
||||
padding: 4px;
|
||||
}
|
127
static/util.js
Normal file
127
static/util.js
Normal file
@ -0,0 +1,127 @@
|
||||
console.log("entered script");
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const questionTypeElement = document.getElementById('question_type');
|
||||
|
||||
console.log("dom content loaded!");
|
||||
|
||||
if (questionTypeElement) {
|
||||
questionTypeElement.addEventListener('change', function () {
|
||||
const optionsSection = document.getElementById('options-section');
|
||||
const selectedType = this.value;
|
||||
if (selectedType === 'multiple_choice' || selectedType === 'single_choice' || selectedType === 'dropdown') {
|
||||
optionsSection.style.display = 'block';
|
||||
} else {
|
||||
optionsSection.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('input[id^="other_"]').forEach(el => {
|
||||
const id = el.id.split('_')[1];
|
||||
const otherTextElement = document.getElementById(`other_text_${id}`);
|
||||
if (otherTextElement) {
|
||||
el.addEventListener('change', function () {
|
||||
otherTextElement.style.display = this.checked ? 'block' : 'none';
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('input[id^="radio_other_"]').forEach(el => {
|
||||
const id = el.id.split('_')[2];
|
||||
const radioOtherTextElement = document.getElementById(`radio_other_text_${id}`);
|
||||
if (radioOtherTextElement) {
|
||||
el.addEventListener('change', function () {
|
||||
radioOtherTextElement.style.display = this.checked ? 'block' : 'none';
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const tagsInput = document.getElementById('classes-input');
|
||||
let classes = [];
|
||||
|
||||
// Define the addClass function before it's used
|
||||
function addClass(classname) {
|
||||
const container = document.getElementById('classes-container');
|
||||
const classElement = document.createElement('span');
|
||||
classElement.classList.add('tag');
|
||||
classElement.textContent = classname;
|
||||
|
||||
const closeIcon = document.createElement('span');
|
||||
closeIcon.classList.add('close');
|
||||
closeIcon.textContent = 'x';
|
||||
closeIcon.addEventListener('click', function () {
|
||||
container.removeChild(classElement);
|
||||
classes = classes.filter(c => c !== classname);
|
||||
fetchMatchingPeople(classes); // Call fetchMatchingPeople when a class is removed
|
||||
});
|
||||
|
||||
classElement.appendChild(closeIcon);
|
||||
container.appendChild(classElement);
|
||||
}
|
||||
|
||||
// Define the fetchMatchingPeople function before it's used
|
||||
function fetchMatchingPeople(classes) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/fetch-matches", true);
|
||||
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
||||
|
||||
xhr.onload = function () {
|
||||
if (xhr.status === 200) {
|
||||
const matchesContainer = document.getElementById('matches-container');
|
||||
const people = JSON.parse(xhr.responseText);
|
||||
|
||||
matchesContainer.innerHTML = '';
|
||||
|
||||
// Log the response to see if "people" is populated
|
||||
console.log("Fetched people: ", people);
|
||||
|
||||
if (people && people.length > 0) {
|
||||
people.forEach(person => {
|
||||
const personDiv = document.createElement('div');
|
||||
personDiv.classList.add('person');
|
||||
personDiv.innerHTML = `<strong>${person.name}</strong><br>Classes: ${person.classes.join(', ')}<br>Email: ${person.email || 'N/A'}`;
|
||||
matchesContainer.appendChild(personDiv);
|
||||
});
|
||||
} else {
|
||||
matchesContainer.innerHTML = '<p>No matches found for these classes.</p>';
|
||||
}
|
||||
} else {
|
||||
console.error("Error fetching matches:", xhr.status, xhr.responseText);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function () {
|
||||
console.error("Network error while fetching matches.");
|
||||
};
|
||||
|
||||
xhr.send(JSON.stringify({ classes: classes }));
|
||||
}
|
||||
|
||||
tagsInput.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Enter' || event.key === ',') {
|
||||
event.preventDefault();
|
||||
let value = tagsInput.value.trim().replace(/,$/, '');
|
||||
if (value) {
|
||||
addClass(value); // Add class tag
|
||||
classes.push(value); // Add to classes array
|
||||
fetchMatchingPeople(classes); // Fetch matching people
|
||||
tagsInput.value = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('form').addEventListener('submit', function (event) {
|
||||
console.log('Classes on submit:', classes);
|
||||
const hiddenClassesInput = document.createElement('input');
|
||||
hiddenClassesInput.type = 'hidden';
|
||||
hiddenClassesInput.name = 'classes';
|
||||
|
||||
// Filter out empty class names and trim spaces
|
||||
const cleanedClasses = classes.filter(classname => classname.trim() !== "").map(classname => classname.trim());
|
||||
hiddenClassesInput.value = cleanedClasses.join(',');
|
||||
|
||||
this.appendChild(hiddenClassesInput);
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user