package main import ( "database/sql" "encoding/json" "html/template" "log" "net/http" "time" "github.com/gorilla/mux" "github.com/gorilla/sessions" _ "github.com/mattn/go-sqlite3" "golang.org/x/crypto/bcrypt" ) var ( db *sql.DB store = sessions.NewCookieStore([]byte("super-secret-key")) templates *template.Template ) type User struct { ID int Username string Password string } type Message struct { ID int `json:"id"` SenderID int `json:"sender_id"` Username string `json:"username"` Content string `json:"content"` Timestamp time.Time `json:"timestamp"` } func init() { var err error templates, err = template.ParseFiles("templates/login.html", "templates/chat.html") if err != nil { log.Fatal(err) } } func main() { var err error db, err = sql.Open("sqlite3", "./chat.db") if err != nil { log.Fatal(err) } defer db.Close() createTables() prepopulateUsers() // Configure session store for non-HTTPS compatibility store.Options = &sessions.Options{ Path: "/", HttpOnly: true, SameSite: http.SameSiteLaxMode, // Lax mode for Safari compatibility on HTTP } r := mux.NewRouter() r.HandleFunc("/", rootHandler) // Redirect root to login r.HandleFunc("/login", loginPage).Methods("GET") r.HandleFunc("/login", loginHandler).Methods("POST") r.HandleFunc("/logout", logoutHandler).Methods("GET") r.HandleFunc("/chat", chatPage).Methods("GET") r.HandleFunc("/send", sendMessage).Methods("POST") r.HandleFunc("/messages", getMessages).Methods("GET") r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static")))) http.Handle("/", r) log.Println("Server starting on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) } func rootHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/login", http.StatusSeeOther) } func createTables() { _, err := db.Exec(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE, password TEXT ); CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, sender_id INTEGER, content TEXT, timestamp DATETIME, FOREIGN KEY(sender_id) REFERENCES users(id) ); `) if err != nil { log.Fatal(err) } } func prepopulateUsers() { users := []User{ //{Username: "user1", Password: "pass1"}, //{Username: "user2", Password: "pass2"}, } for _, u := range users { hashedPass, _ := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost) _, err := db.Exec("INSERT OR IGNORE INTO users (username, password) VALUES (?, ?)", u.Username, hashedPass) if err != nil { log.Fatal(err) } } } func loginPage(w http.ResponseWriter, r *http.Request) { session, _ := store.Get(r, "session") if session.Values["username"] != nil { http.Redirect(w, r, "/chat", http.StatusSeeOther) return } errorMsg := r.URL.Query().Get("error") data := struct { Error string }{ Error: errorMsg, } templates.ExecuteTemplate(w, "login.html", data) } func loginHandler(w http.ResponseWriter, r *http.Request) { r.ParseForm() username := r.FormValue("username") password := r.FormValue("password") var storedHash string err := db.QueryRow("SELECT password FROM users WHERE username = ?", username).Scan(&storedHash) if err != nil || bcrypt.CompareHashAndPassword([]byte(storedHash), []byte(password)) != nil { http.Redirect(w, r, "/login?error=invalid", http.StatusSeeOther) return } session, _ := store.Get(r, "session") session.Values["username"] = username session.Save(r, w) http.Redirect(w, r, "/chat", http.StatusSeeOther) } func chatPage(w http.ResponseWriter, r *http.Request) { session, _ := store.Get(r, "session") if session.Values["username"] == nil { http.Redirect(w, r, "/login", http.StatusSeeOther) return } templates.ExecuteTemplate(w, "chat.html", session.Values["username"]) } func sendMessage(w http.ResponseWriter, r *http.Request) { session, _ := store.Get(r, "session") if session.Values["username"] == nil { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } r.ParseForm() content := r.FormValue("content") username := session.Values["username"].(string) var senderID int db.QueryRow("SELECT id FROM users WHERE username = ?", username).Scan(&senderID) _, err := db.Exec("INSERT INTO messages (sender_id, content, timestamp) VALUES (?, ?, ?)", senderID, content, time.Now().UTC()) if err != nil { http.Error(w, "Failed to send message", http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) } func getMessages(w http.ResponseWriter, r *http.Request) { session, _ := store.Get(r, "session") if session.Values["username"] == nil { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } rows, err := db.Query(` SELECT m.id, m.sender_id, u.username, m.content, m.timestamp FROM messages m JOIN users u ON m.sender_id = u.id ORDER BY m.timestamp ASC `) if err != nil { http.Error(w, "Failed to fetch messages", http.StatusInternalServerError) return } defer rows.Close() var messages []Message for rows.Next() { var m Message rows.Scan(&m.ID, &m.SenderID, &m.Username, &m.Content, &m.Timestamp) messages = append(messages, m) } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(messages) } func logoutHandler(w http.ResponseWriter, r *http.Request) { session, _ := store.Get(r, "session") session.Options.MaxAge = -1 session.Save(r, w) http.Redirect(w, r, "/login", http.StatusSeeOther) }