diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c537ea5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Remove database +formdata.db diff --git a/admin.html b/admin.html new file mode 100644 index 0000000..32d16c7 --- /dev/null +++ b/admin.html @@ -0,0 +1,35 @@ + + + + + + Admin - Submissions + + + +
+

Submitted Answers

+ + + + + + + + {{range .}} + + + + + {{else}} + + + + {{end}} +
QuestionAnswer
{{.QuestionText}}{{.Answer}}
No submissions found.
+
+ + + + + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8d63baa --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module ergosthing + +go 1.23.0 + +require github.com/mattn/go-sqlite3 v1.14.22 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e8d092a --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= diff --git a/index.html b/index.html new file mode 100644 index 0000000..9729640 --- /dev/null +++ b/index.html @@ -0,0 +1,25 @@ + + + + + + Form + + + +
+

Submit Your Information

+ +
+ {{range .}} + +

+ {{end}} + + +
+
+ + + + diff --git a/main.go b/main.go new file mode 100644 index 0000000..eeb1c76 --- /dev/null +++ b/main.go @@ -0,0 +1,235 @@ +package main + +import ( + "database/sql" + "fmt" + "html/template" + "log" + "net/http" + _ "github.com/mattn/go-sqlite3" +) + + +type Question struct { + ID int + QuestionText string + QuestionType string +} + +var db *sql.DB + +func renderForm(w http.ResponseWriter, r *http.Request) { + rows, err := db.Query("SELECT 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.QuestionText, &question.QuestionType) + if err != nil { + log.Printf("Error scanning question: %v\n", err) + continue + } + questions = append(questions, question) + } + + tmpl := template.Must(template.ParseFiles("index.html")) + tmpl.Execute(w, questions) +} + +func handleFormSubmit(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Invalid request method", http.StatusMethodNotAllowed) + return + } + + r.ParseForm() + + rows, err := db.Query("SELECT id FROM questions") + 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 questionID int + for rows.Next() { + err := rows.Scan(&questionID) + if err != nil { + log.Printf("Error scanning question ID: %v\n", err) + continue + } + fieldName := fmt.Sprintf("custom_%d", questionID) + fieldValue := r.FormValue(fieldName) + + if fieldValue != "" { + insertQuery := `INSERT INTO answers (question_id, answer) VALUES (?, ?)` + _, err = db.Exec(insertQuery, questionID, fieldValue) + if err != nil { + log.Printf("Error saving answer: %v\n", err) + http.Error(w, "Failed to save answer", http.StatusInternalServerError) + return + } + } else { + log.Printf("No answer found for question %d", questionID) + } + } + + fmt.Fprintf(w, "Thank you for your submission!") +} + + +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 + `) + if err != nil { + log.Printf("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 + } + + for rows.Next() { + var submission struct { + QuestionText string + Answer string + } + err := rows.Scan(&submission.QuestionText, &submission.Answer) + if err != nil { + log.Printf("Error scanning submission: %v\n", err) + continue + } + submissions = append(submissions, submission) + } + + tmpl := template.Must(template.ParseFiles("admin.html")) + tmpl.Execute(w, submissions) +} + +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) +} + + +func main() { + var err error + + db, err = sql.Open("sqlite3", "./formdata.db") + 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 answers ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + question_id INTEGER NOT NULL, + answer TEXT NOT NULL, + FOREIGN KEY (question_id) REFERENCES questions(id) + ); + ` + + _, err = db.Exec(createTableQueries) + if err != nil { + log.Fatal(err) + return + } + + 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) + + fmt.Println("Server started at :8080") + + log.Fatal(http.ListenAndServe(":8080", nil)) +} diff --git a/manage.html b/manage.html new file mode 100644 index 0000000..8ed607e --- /dev/null +++ b/manage.html @@ -0,0 +1,44 @@ + + + + + + Manage Questions + + + +
+

Manage Questions

+ + + + +

Add New Question

+
+ +

+ +
+ +

Existing Questions

+ + + + + + {{range .}} + + + + + {{else}} + + + + {{end}} +
Question TextActions
{{.QuestionText}}Remove
No questions found.
+
+ + + + diff --git a/output.txt b/output.txt new file mode 100644 index 0000000..3873e61 --- /dev/null +++ b/output.txt @@ -0,0 +1,502 @@ + +--- O:\Web Development\ErgosThing\admin.html --- + + + + + + Admin - Submissions + + + +
+

Submitted Answers

+ + + + + + {{range .}} + + + + + {{else}} + + + + {{end}} +
QuestionAnswer
{{.QuestionText}}{{.Answer}}
No submissions found.
+
+ + + +END --- O:\Web Development\ErgosThing\admin.html + +--- O:\Web Development\ErgosThing\go.mod --- +module ergosthing + +go 1.23.0 + +require github.com/mattn/go-sqlite3 v1.14.22 + +END --- O:\Web Development\ErgosThing\go.mod + +--- O:\Web Development\ErgosThing\go.sum --- +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= + +END --- O:\Web Development\ErgosThing\go.sum + +--- O:\Web Development\ErgosThing\index.html --- + + + + + + Form + + + +
+

Submit Your Information

+
+ {{range .}} + +

+ {{end}} + + +
+
+ + + +END --- O:\Web Development\ErgosThing\index.html + +--- O:\Web Development\ErgosThing\main.go --- +package main + +import ( + "database/sql" + "fmt" + "html/template" + "log" + "net/http" + _ "github.com/mattn/go-sqlite3" +) + + +type Question struct { + ID int + QuestionText string + QuestionType string +} + +var db *sql.DB + +func renderForm(w http.ResponseWriter, r *http.Request) { + rows, err := db.Query("SELECT 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.QuestionText, &question.QuestionType) + if err != nil { + log.Printf("Error scanning question: %v\n", err) + continue + } + questions = append(questions, question) + } + + tmpl := template.Must(template.ParseFiles("index.html")) + tmpl.Execute(w, questions) +} + +func handleFormSubmit(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Invalid request method", http.StatusMethodNotAllowed) + return + } + + r.ParseForm() + + rows, err := db.Query("SELECT id FROM questions") + 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 questionID int + for rows.Next() { + err := rows.Scan(&questionID) + if err != nil { + log.Printf("Error scanning question ID: %v\n", err) + continue + } + fieldName := fmt.Sprintf("custom_%d", questionID) + fieldValue := r.FormValue(fieldName) + + if fieldValue != "" { + insertQuery := `INSERT INTO answers (question_id, answer) VALUES (?, ?)` + _, err = db.Exec(insertQuery, questionID, fieldValue) + if err != nil { + log.Printf("Error saving answer: %v\n", err) + http.Error(w, "Failed to save answer", http.StatusInternalServerError) + return + } + } else { + log.Printf("No answer found for question %d", questionID) + } + } + + fmt.Fprintf(w, "Thank you for your submission!") +} + + +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 + `) + if err != nil { + log.Printf("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 + } + + for rows.Next() { + var submission struct { + QuestionText string + Answer string + } + err := rows.Scan(&submission.QuestionText, &submission.Answer) + if err != nil { + log.Printf("Error scanning submission: %v\n", err) + continue + } + submissions = append(submissions, submission) + } + + tmpl := template.Must(template.ParseFiles("admin.html")) + tmpl.Execute(w, submissions) +} + +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) +} + + +func main() { + var err error + + db, err = sql.Open("sqlite3", "./formdata.db") + 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 answers ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + question_id INTEGER NOT NULL, + answer TEXT NOT NULL, + FOREIGN KEY (question_id) REFERENCES questions(id) + ); + ` + + _, err = db.Exec(createTableQueries) + if err != nil { + log.Fatal(err) + return + } + + 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) + + fmt.Println("Server started at :8080") + + log.Fatal(http.ListenAndServe(":8080", nil)) +} + +END --- O:\Web Development\ErgosThing\main.go + +--- O:\Web Development\ErgosThing\manage.html --- + + + + + + Manage Questions + + + +
+

Manage Questions

+

Add New Question

+
+ +

+ +
+ +

Existing Questions

+ + + + + + {{range .}} + + + + + {{else}} + + + + {{end}} +
Question TextActions
{{.QuestionText}}Remove
No questions found.
+
+ + + +END --- O:\Web Development\ErgosThing\manage.html + +--- O:\Web Development\ErgosThing\typer.py --- +import os +import sys +from pathspec import PathSpec +from pathspec.patterns import GitWildMatchPattern + +def load_gitignore_patterns(gitignore_file): + with open(gitignore_file, 'r') as f: + lines = f.readlines() + spec = PathSpec.from_lines(GitWildMatchPattern, lines) + return spec + +def print_file_content(file_path, output_file): + with open(file_path, 'r', encoding='utf-8', errors='ignore') as file: + content = file.read() + output = f"\n--- {file_path} ---\n{content}\nEND --- {file_path}\n" + print(output) + with open(output_file, 'a', encoding='utf-8') as out_file: + out_file.write(output) + +def walk_and_print(directory, spec, gitignore_path, output_file, exclude_cmakelists): + # Overwrite the output file if it exists + if os.path.exists(output_file): + os.remove(output_file) + + for root, dirs, files in os.walk(directory): + # Always ignore 'assets' and 'art' directories + dirs[:] = [d for d in dirs if d not in ('assets', 'art') and not spec.match_file(os.path.relpath(os.path.join(root, d), directory))] + files = [f for f in files if not spec.match_file(os.path.relpath(os.path.join(root, f), directory))] + + if exclude_cmakelists: + files = [f for f in files if f != 'CMakeLists.txt'] + + for file_name in files: + file_path = os.path.join(root, file_name) + # Exclude the .gitignore file itself from being printed + if os.path.abspath(file_path) != os.path.abspath(gitignore_path): + print_file_content(file_path, output_file) + +if __name__ == "__main__": + current_directory = os.getcwd() + gitignore_path = os.path.join(current_directory, '.gitignore') + output_file_path = os.path.join(current_directory, 'output.txt') + + exclude_cmakelists = '-nocmake' in sys.argv + + if os.path.exists(gitignore_path): + spec = load_gitignore_patterns(gitignore_path) + else: + spec = PathSpec([]) # Empty spec if no .gitignore is found + + walk_and_print(current_directory, spec, gitignore_path, output_file_path, exclude_cmakelists) + +END --- O:\Web Development\ErgosThing\typer.py + +--- O:\Web Development\ErgosThing\static\style.css --- +body { + font-family: Arial, sans-serif; + background-color: #f4f4f9; + margin: 0; + padding: 0; +} + +.container { + width: 80%; + margin: 0 auto; + padding: 20px; + background-color: white; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + margin-top: 50px; + border-radius: 10px; +} + +h1 { + text-align: center; + color: #333; +} + +form { + margin: 20px 0; +} + +form label { + display: block; + margin-bottom: 10px; + font-weight: bold; + color: #555; +} + +form input[type="text"], form input[type="email"], form textarea { + width: 100%; + padding: 10px; + margin-bottom: 20px; + border: 1px solid #ccc; + border-radius: 5px; + font-size: 16px; +} + +form input[type="submit"] { + background-color: #28a745; + color: white; + padding: 10px 20px; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 16px; +} + +form input[type="submit"]:hover { + background-color: #218838; +} + +table { + width: 100%; + border-collapse: collapse; + margin-top: 20px; +} + +table, th, td { + border: 1px solid #ddd; +} + +th, td { + padding: 10px; + text-align: left; +} + +th { + background-color: #f2f2f2; +} + +td { + background-color: white; +} + +a { + color: #007bff; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +END --- O:\Web Development\ErgosThing\static\style.css diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..3bdf986 --- /dev/null +++ b/static/style.css @@ -0,0 +1,147 @@ +body { + font-family: Arial, sans-serif; + background-color: #f4f4f9; + color: #333; + margin: 0; + padding: 0; +} + +.container { + width: 80%; + margin: 0 auto; + padding: 20px; + background-color: white; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + margin-top: 50px; + border-radius: 10px; +} + +h1 { + text-align: center; + color: #333; /* Default color for light mode */ +} + +form { + margin: 20px 0; +} + +form label { + display: block; + margin-bottom: 10px; + font-weight: bold; + color: #555; +} + +form input[type="text"], form input[type="email"], form textarea { + width: 100%; + padding: 10px; + margin-bottom: 20px; + border: 1px solid #ccc; + border-radius: 5px; + font-size: 16px; + background-color: white; + color: #333; +} + +form input[type="submit"] { + background-color: #28a745; + color: white; + padding: 10px 20px; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 16px; +} + +form input[type="submit"]:hover { + background-color: #218838; +} + +table { + width: 100%; + border-collapse: collapse; + margin-top: 20px; +} + +table, th, td { + border: 1px solid #ddd; +} + +th, td { + padding: 10px; + text-align: left; +} + +th { + background-color: #f2f2f2; + color: #333; +} + +td { + background-color: white; + color: #333; +} + +a { + color: #007bff; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +/* + * DARK MODE + */ +body.dark-mode { + background-color: #1a1a1a; + color: #f4f4f9; +} + +.container.dark-mode { + background-color: #333; + box-shadow: 0 0 10px rgba(255, 255, 255, 0.1); +} + +h1.dark-mode { + color: #f4f4f9; /* Ensure that the heading is white and visible */ +} + +form label.dark-mode { + color: #f4f4f9; +} + +form input[type="text"].dark-mode, form input[type="email"].dark-mode, form textarea.dark-mode { + background-color: #555; + color: #f4f4f9; + border: 1px solid #888; +} + +form input[type="submit"].dark-mode { + background-color: #28a745; + color: white; +} + +table.dark-mode { + background-color: #444; +} + +th.dark-mode { + background-color: #555; + color: #f4f4f9; +} + +td.dark-mode { + background-color: #666; + color: #f4f4f9; +} + +a.dark-mode { + color: #66b3ff; +} + +a.dark-mode:hover { + color: #3399ff; + text-decoration: underline; +} diff --git a/static/theme.js b/static/theme.js new file mode 100644 index 0000000..0f13072 --- /dev/null +++ b/static/theme.js @@ -0,0 +1,23 @@ +document.addEventListener("DOMContentLoaded", function () { + const darkModeToggle = document.getElementById('dark-mode-toggle'); + + if (localStorage.getItem('theme') === 'dark') { + document.body.classList.add('dark-mode'); + document.querySelectorAll('.container, label, th, td, a, table, h1').forEach(el => { + el.classList.add('dark-mode'); + }); + } + + darkModeToggle.addEventListener('click', function () { + document.body.classList.toggle('dark-mode'); + document.querySelectorAll('.container, label, th, td, a, table, h1').forEach(el => { + el.classList.toggle('dark-mode'); + }); + + if (document.body.classList.contains('dark-mode')) { + localStorage.setItem('theme', 'dark'); + } else { + localStorage.setItem('theme', 'light'); + } + }); +});