diff --git a/config.json b/config.json
index 503e709..bb9b9f7 100644
--- a/config.json
+++ b/config.json
@@ -5,10 +5,9 @@
},
"paths": {
"databasePath": "/home/radon/Documents/chattest.db",
- "indexJsPath": "./content/root.js",
- "indexCssPath": "./content/root.css",
- "indexHtmlPath": "./content/root.html",
- "messagesHtmlPath": "./content/messages.html"
+ "indexJsPath": "./public/index.js",
+ "indexCssPath": "./public/style.css",
+ "indexHtmlPath": "./public/index.html"
},
"options": {
"messageMaxAge": 259200,
diff --git a/content/messages.html b/content/messages.html
deleted file mode 100644
index 2e97c0c..0000000
--- a/content/messages.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- {{body}}
-
-
-
diff --git a/db/db.go b/db/db.go
new file mode 100644
index 0000000..c443fbf
--- /dev/null
+++ b/db/db.go
@@ -0,0 +1,326 @@
+package db
+
+import (
+ "database/sql"
+ "fmt"
+ _ "github.com/mattn/go-sqlite3"
+ "strconv"
+ "time"
+)
+
+type User struct {
+ Id string
+ Username string
+ IpAddress string
+ Timezone string
+}
+
+type Message struct {
+ Id string
+ SenderIp string
+ SenderUsername string
+ Content string
+ Timestamp string
+ Edited bool
+}
+
+type Database struct {
+ db *sql.DB
+}
+
+func OpenDatabase(filepath string) *Database {
+ if db, err := sql.Open("sqlite3", filepath); err != nil {
+ return nil
+ } else {
+ return &Database{
+ db: db,
+ }
+ }
+}
+
+func (db *Database) Close() {
+ db.db.Close()
+}
+
+func (db *Database) DbCreateTableMessages() {
+ stmt := `CREATE TABLE IF NOT EXISTS messages (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ ip_address TEXT NOT NULL,
+ content TEXT NOT NULL,
+ edited INTEGER DEFAULT 0,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )`
+ db.db.Exec(stmt)
+}
+
+func (db *Database) DbCreateTableUsers() {
+ stmt := `CREATE TABLE IF NOT EXISTS users (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ ip_address TEXT NOT NULL,
+ username TEXT NOT NULL UNIQUE,
+ timezone TEXT DEFAULT 'America/New_York',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )`
+ db.db.Exec(stmt)
+}
+
+func (db *Database) UserTimezoneSet(ip_address, timezone string) {
+ _, err := db.db.Exec("UPDATE users SET timezone = ? WHERE ip_address = ?", timezone, ip_address)
+ if err != nil {
+ fmt.Println(err)
+ }
+}
+
+func (db *Database) UserAdd(ip_address, username string) {
+ _, err := db.db.Exec("INSERT INTO users (username, ip_address) VALUES (?, ?)", username, ip_address)
+ if err != nil {
+ fmt.Println(err)
+ }
+}
+
+func (db *Database) MessageAdd(ip_address string, content string) {
+ _, err := db.db.Exec("INSERT INTO messages (ip_address, content) VALUES (?, ?)", ip_address, content)
+ if err != nil {
+ fmt.Println(err)
+ }
+}
+
+func (db *Database) UserNameGet(ip_address string) string {
+ rows, err := db.db.Query("SELECT username FROM users WHERE ip_address = ?", ip_address)
+ if err != nil {
+ fmt.Println(err)
+ }
+ defer rows.Close()
+ var username string
+ rows.Next()
+ rows.Scan(&username)
+ return username
+}
+
+func (db *Database) UserIpGet(username string) string {
+ rows, err := db.db.Query("SELECT ip_address FROM users WHERE username = ?", username)
+ if err != nil {
+ fmt.Println(err)
+ }
+ defer rows.Close()
+ var ip_address string
+ rows.Next()
+ rows.Scan(&ip_address)
+ return ip_address
+}
+
+func (db *Database) UserGetTimezone(ip_address string) string {
+ rows, err := db.db.Query("SELECT timezone FROM users WHERE ip_address = ?", ip_address)
+ if err != nil {
+ fmt.Println(err)
+ }
+ defer rows.Close()
+ var timezone string
+ rows.Next()
+ rows.Scan(&timezone)
+ return timezone
+}
+
+func (db *Database) UsersGet() []User {
+ rows, err := db.db.Query("SELECT * FROM users")
+ if err != nil {
+ fmt.Println(err)
+ }
+ defer rows.Close()
+ var users []User
+ for rows.Next() {
+ var id string
+ var ip_address string
+ var username string
+ var created_at string
+ var timezone string
+ rows.Scan(&id, &ip_address, &username, &created_at, &timezone)
+ user := User{
+ Id: id,
+ Username: username,
+ IpAddress: ip_address,
+ Timezone: timezone,
+ }
+ users = append(users, user)
+ }
+ return users
+}
+
+func (db *Database) MessagesGet() []Message {
+ rows, err := db.db.Query(`
+ SELECT messages.id, messages.ip_address, messages.content,
+ strftime('%Y-%m-%d %H:%M:%S', messages.created_at) as created_at,
+ users.username, messages.edited
+ FROM messages
+ LEFT JOIN users ON messages.ip_address = users.ip_address;
+ `)
+ if err != nil {
+ fmt.Println(err)
+ }
+ defer rows.Close()
+
+ var messages []Message
+
+ for rows.Next() {
+ var id string
+ var content string
+ var ip_address string
+ var created_at string
+ var username string
+ var edited int
+ rows.Scan(&id, &ip_address, &content, &created_at, &username, &edited)
+
+ editedBool := false
+ if edited == 1 {
+ editedBool = true
+ }
+
+ message := Message{
+ Id: id,
+ Content: content,
+ SenderIp: ip_address,
+ SenderUsername: username,
+ Edited: editedBool,
+ Timestamp: created_at,
+ }
+
+ messages = append(messages, message)
+ }
+ return messages
+}
+
+func (db *Database) UserNameExists(username string) bool {
+ rows, err := db.db.Query("SELECT * FROM users WHERE username = ?", username)
+ if err != nil {
+ fmt.Println(err)
+ }
+ defer rows.Close()
+ return rows.Next()
+}
+
+func (db *Database) UserExists(ip string) bool {
+ rows, err := db.db.Query("SELECT * FROM users WHERE ip_address = ?", ip)
+ if err != nil {
+ fmt.Println(err)
+ }
+ defer rows.Close()
+ return rows.Next()
+}
+
+func (db *Database) UserNameChange(ip, newUsername string) {
+ _, err := db.db.Exec("UPDATE users SET username = ? WHERE ip_address = ?", newUsername, ip)
+ if err != nil {
+ fmt.Println(err)
+ }
+}
+
+func (db *Database) UserMessagesDelete(ip string) {
+ _, err := db.db.Exec("DELETE FROM messages WHERE ip_address = ?", ip)
+ if err != nil {
+ fmt.Println(err)
+ }
+}
+
+func (db *Database) UserMessagesGet(ip string) []Message {
+ rows, err := db.db.Query(`
+ SELECT messages.*, users.username
+ FROM messages
+ LEFT JOIN users ON messages.ip_address = users.ip_address
+ WHERE messages.ip_address = ?
+ ORDER BY messages.created_at DESC;
+ `, ip)
+ if err != nil {
+ fmt.Println(err)
+ }
+ defer rows.Close()
+
+ var messages []Message
+
+ for rows.Next() {
+ var id string
+ var content string
+ var ip_address string
+ var created_at string
+ var username string
+ var edited int
+ rows.Scan(&id, &ip_address, &content, &created_at, &username, &edited)
+ t, _ := time.Parse(created_at, created_at)
+ editedBool := false
+ if edited == 1 {
+ editedBool = true
+ }
+ message := Message{
+ Id: id,
+ Content: content,
+ SenderIp: ip_address,
+ SenderUsername: username,
+ Edited: editedBool,
+ Timestamp: t.Format(created_at),
+ }
+ messages = append(messages, message)
+ }
+ return messages
+}
+
+func (db *Database) MessageDeleteId(id string) {
+ _, err := db.db.Exec("DELETE FROM messages WHERE id = ?", id)
+ if err != nil {
+ fmt.Println(err)
+ }
+}
+func (db *Database) MessageDeleteIfOwner(id string, ip string) (int, error) {
+ res, err := db.db.Exec("DELETE FROM messages WHERE id = ? AND ip_address = ?", id, ip)
+ if err != nil {
+ return 0, err
+ }
+ affected, err := res.RowsAffected()
+ if err != nil {
+ return 0, err
+ }
+ return int(affected), nil
+
+}
+
+func (db *Database) MessageEditIfOwner(id string, content string, ip string) (int, error) {
+ res, err := db.db.Exec("UPDATE messages SET content = ?, edited = 1 WHERE id = ? AND ip_address = ?", content, id, ip)
+ if err != nil {
+ return 0, err
+ }
+ affected, err := res.RowsAffected()
+ if err != nil {
+ return 0, err
+ }
+ return int(affected), nil
+}
+
+func (db *Database) DeleteOldMessages(ageMinutes int) {
+ if ageMinutes <= 0 {
+ return
+ }
+ age := strconv.Itoa(ageMinutes)
+ _, err := db.db.Exec("DELETE FROM messages WHERE created_at < datetime('now', ? || ' minutes')", "-"+age)
+ if err != nil {
+ fmt.Println(err)
+ }
+}
+
+func (db *Database) UserDeleteIp(ip string) {
+ _, err := db.db.Exec("DELETE FROM users WHERE ip_address = ?", ip)
+ if err != nil {
+ fmt.Println(err)
+ }
+}
+
+func (db *Database) UsersDelete() {
+ _, err := db.db.Exec("DELETE FROM users")
+ if err != nil {
+ fmt.Println(err)
+ }
+}
+
+func (db *Database) MessagesDelete() {
+ _, err := db.db.Exec("DELETE FROM messages")
+ if err != nil {
+ fmt.Println(err)
+ }
+}
diff --git a/main.go b/main.go
index b7273d4..0691d0d 100644
--- a/main.go
+++ b/main.go
@@ -1,789 +1,22 @@
package main
import (
- "compress/gzip"
- "database/sql"
- "encoding/json"
+ "chat/srv"
"fmt"
- _ "github.com/mattn/go-sqlite3"
- "io"
- "net"
- "net/http"
"os"
- "path/filepath"
- "strconv"
- "strings"
- "sync"
- "time"
)
-type Database struct {
- db *sql.DB
-}
+var config srv.Config
-func TimezoneToLocation(timezone string) *time.Location {
- defaultLocation := time.FixedZone("UTC", 0)
- location, err := time.LoadLocation(timezone)
- if err != nil {
- return defaultLocation
- } else {
- return location
- }
-}
-
-func TimeStringToTime(timeString string) time.Time {
- t, _ := time.Parse("2006-01-02 15:04:05", timeString)
- return t
-}
-
-func TimeStringToTimeInLocation(timeString string, timezone string) string {
- t := TimeStringToTime(timeString)
- location := TimezoneToLocation(timezone)
- return t.In(location).Format("2006-01-02 15:04:05")
-}
-
-func OpenDatabase(filepath string) *Database {
- if db, err := sql.Open("sqlite3", filepath); err != nil {
- return nil
- } else {
- return &Database{
- db: db,
- }
- }
-}
-
-func (db *Database) Close() {
- db.db.Close()
-}
-
-func (db *Database) DbCreateTableMessages() {
- stmt := `CREATE TABLE IF NOT EXISTS messages (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- ip_address TEXT NOT NULL,
- content TEXT NOT NULL,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- )`
- db.db.Exec(stmt)
-}
-
-func (db *Database) DbCreateTableUsers() {
- stmt := `CREATE TABLE IF NOT EXISTS users (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- ip_address TEXT NOT NULL,
- username TEXT NOT NULL UNIQUE,
- timezone TEXT DEFAULT 'America/New_York',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- )`
- db.db.Exec(stmt)
-}
-
-func (db *Database) UserTimezoneSet(ip_address, timezone string) {
- _, err := db.db.Exec("UPDATE users SET timezone = ? WHERE ip_address = ?", timezone, ip_address)
- if err != nil {
- fmt.Println(err)
- }
-}
-
-func (db *Database) UserAdd(ip_address, username string) {
- _, err := db.db.Exec("INSERT INTO users (username, ip_address) VALUES (?, ?)", username, ip_address)
- if err != nil {
- fmt.Println(err)
- }
-}
-
-func (db *Database) MessageAdd(ip_address string, content string) {
- _, err := db.db.Exec("INSERT INTO messages (ip_address, content) VALUES (?, ?)", ip_address, content)
- if err != nil {
- fmt.Println(err)
- }
-}
-
-func (db *Database) UserNameGet(ip_address string) string {
- rows, err := db.db.Query("SELECT username FROM users WHERE ip_address = ?", ip_address)
- if err != nil {
- fmt.Println(err)
- }
- defer rows.Close()
- var username string
- rows.Next()
- rows.Scan(&username)
- return username
-}
-
-func (db *Database) UserIpGet(username string) string {
- rows, err := db.db.Query("SELECT ip_address FROM users WHERE username = ?", username)
- if err != nil {
- fmt.Println(err)
- }
- defer rows.Close()
- var ip_address string
- rows.Next()
- rows.Scan(&ip_address)
- return ip_address
-}
-
-func (db *Database) UserGetTimezone(ip_address string) string {
- rows, err := db.db.Query("SELECT timezone FROM users WHERE ip_address = ?", ip_address)
- if err != nil {
- fmt.Println(err)
- }
- defer rows.Close()
- var timezone string
- rows.Next()
- rows.Scan(&timezone)
- return timezone
-}
-
-func (db *Database) UsersGet() []User {
- rows, err := db.db.Query("SELECT * FROM users")
- if err != nil {
- fmt.Println(err)
- }
- defer rows.Close()
- var users []User
- for rows.Next() {
- var id string
- var ip_address string
- var username string
- var created_at string
- var timezone string
- rows.Scan(&id, &ip_address, &username, &created_at, &timezone)
- user := User{
- Id: id,
- Username: username,
- IpAddress: ip_address,
- Timezone: timezone,
- }
- users = append(users, user)
- }
- return users
-}
-
-func (db *Database) MessagesGet() []Message {
- rows, err := db.db.Query(`
- SELECT messages.id, messages.ip_address, messages.content,
- strftime('%Y-%m-%d %H:%M:%S', messages.created_at) as created_at,
- users.username
- FROM messages
- LEFT JOIN users ON messages.ip_address = users.ip_address;
- `)
- if err != nil {
- fmt.Println(err)
- }
- defer rows.Close()
-
- var messages []Message
-
- for rows.Next() {
- var id string
- var content string
- var ip_address string
- var created_at string
- var username string
- rows.Scan(&id, &ip_address, &content, &created_at, &username)
-
- message := Message{
- Id: id,
- Content: content,
- SenderIp: ip_address,
- SenderUsername: username,
- Timestamp: created_at,
- }
-
- messages = append(messages, message)
- }
- return messages
-}
-
-func (db *Database) UserNameExists(username string) bool {
- rows, err := db.db.Query("SELECT * FROM users WHERE username = ?", username)
- if err != nil {
- fmt.Println(err)
- }
- defer rows.Close()
- return rows.Next()
-}
-
-func (db *Database) UserExists(ip string) bool {
- rows, err := db.db.Query("SELECT * FROM users WHERE ip_address = ?", ip)
- if err != nil {
- fmt.Println(err)
- }
- defer rows.Close()
- return rows.Next()
-}
-
-func (db *Database) UserNameChange(ip, newUsername string) {
- _, err := db.db.Exec("UPDATE users SET username = ? WHERE ip_address = ?", newUsername, ip)
- if err != nil {
- fmt.Println(err)
- }
-}
-
-func (db *Database) UserMessagesDelete(ip string) {
- _, err := db.db.Exec("DELETE FROM messages WHERE ip_address = ?", ip)
- if err != nil {
- fmt.Println(err)
- }
-}
-
-func (db *Database) UserMessagesGet(ip string) []Message {
- rows, err := db.db.Query(`
- SELECT messages.*, users.username
- FROM messages
- LEFT JOIN users ON messages.ip_address = users.ip_address
- WHERE messages.ip_address = ?
- ORDER BY messages.created_at DESC;
- `, ip)
- if err != nil {
- fmt.Println(err)
- }
- defer rows.Close()
-
- var messages []Message
-
- for rows.Next() {
- var id string
- var content string
- var ip_address string
- var created_at string
- var username string
- rows.Scan(&id, &ip_address, &content, &created_at, &username)
- t, _ := time.Parse(created_at, created_at)
- message := Message{
- Id: id,
- Content: content,
- SenderIp: ip_address,
- SenderUsername: username,
- Timestamp: t.Format(created_at),
- }
- messages = append(messages, message)
- }
- return messages
-}
-
-func (db *Database) MessageDeleteId(id string) {
- _, err := db.db.Exec("DELETE FROM messages WHERE id = ?", id)
- if err != nil {
- fmt.Println(err)
- }
-}
-func (db *Database) MessageDeleteIfOwner(id string, ip string) (int, error) {
- res, err := db.db.Exec("DELETE FROM messages WHERE id = ? AND ip_address = ?", id, ip)
- if err != nil {
- return 0, err
- }
- affected, err := res.RowsAffected()
- if err != nil {
- return 0, err
- }
- return int(affected), nil
-
-}
-
-func (db *Database) MessageEditIfOwner(id string, content string, ip string) (int, error) {
- res, err := db.db.Exec("UPDATE messages SET content = ? WHERE id = ? AND ip_address = ?", content, id, ip)
- if err != nil {
- return 0, err
- }
- affected, err := res.RowsAffected()
- if err != nil {
- return 0, err
- }
- return int(affected), nil
-}
-
-func (db *Database) DeleteOldMessages(ageMinutes int) {
- if ageMinutes <= 0 {
- return
- }
- age := strconv.Itoa(ageMinutes)
- _, err := db.db.Exec("DELETE FROM messages WHERE created_at < datetime('now', ? || ' minutes')", "-"+age)
- if err != nil {
- fmt.Println(err)
- }
-}
-
-func (db *Database) UserDeleteIp(ip string) {
- _, err := db.db.Exec("DELETE FROM users WHERE ip_address = ?", ip)
- if err != nil {
- fmt.Println(err)
- }
-}
-
-func (db *Database) UsersDelete() {
- _, err := db.db.Exec("DELETE FROM users")
- if err != nil {
- fmt.Println(err)
- }
-}
-
-func (db *Database) MessagesDelete() {
- _, err := db.db.Exec("DELETE FROM messages")
- if err != nil {
- fmt.Println(err)
- }
-}
-
-type gzipResponseWriter struct {
- http.ResponseWriter
- io.Writer
-}
-
-func (g *gzipResponseWriter) Write(data []byte) (int, error) {
- return g.Writer.Write(data)
-}
-
-func GzipMiddleware(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
- next.ServeHTTP(w, r)
- return
- }
-
- gz := gzip.NewWriter(w)
- defer gz.Close()
-
- w.Header().Set("Content-Encoding", "gzip")
- next.ServeHTTP(&gzipResponseWriter{ResponseWriter: w, Writer: gz}, r)
- })
-}
-
-func getClientIP(r *http.Request) string {
- if fwdIP := r.Header.Get("X-Forwarded-For"); fwdIP != "" {
- return strings.Split(fwdIP, ",")[0]
- }
-
- clientIP := r.RemoteAddr
- if host, _, err := net.SplitHostPort(clientIP); err == nil {
- return host
- }
- return clientIP
-}
-
-type User struct {
- Id string
- Username string
- IpAddress string
- Timezone string
-}
-
-type Message struct {
- Id string
- Content string
- SenderIp string
- SenderUsername string
- Timestamp string
-}
-
-type Server struct {
- Connected map[string]time.Time // Map IP -> Last activity time
- Database *Database
- Config Config
- mu sync.Mutex // For thread safety
-}
-
-func NewServer(config Config) *Server {
-
- return &Server{
- Connected: make(map[string]time.Time),
- Database: OpenDatabase(config.Paths.DatabasePath),
- Config: config,
- mu: sync.Mutex{},
- }
-}
-
-func (s *Server) AddMessage(userip string, contents string) {
- s.Database.MessageAdd(userip, contents)
-}
-
-func (s *Server) updateActivity(ip string) {
- s.mu.Lock()
- defer s.mu.Unlock()
- s.Connected[ip] = time.Now()
-}
-
-func (s *Server) cleanupActivity() {
- s.mu.Lock()
- defer s.mu.Unlock()
- for ip, lastActivity := range s.Connected {
- if time.Since(lastActivity) > 10*time.Second {
- delete(s.Connected, ip)
- }
- }
-}
-
-func (s *Server) handlePing(w http.ResponseWriter, r *http.Request) {
- clientIP := getClientIP(r)
- s.updateActivity(clientIP)
- s.cleanupActivity()
- w.WriteHeader(http.StatusOK)
-}
-
-func validUsername(username string) bool {
- for _, c := range username {
- if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') {
- return false
- }
- }
- return true
-}
-
-func (s *Server) handleUsername(w http.ResponseWriter, r *http.Request) {
- if r.Method != http.MethodPut {
- http.Error(w, `{"error": "Method not allowed"}`, http.StatusMethodNotAllowed)
- return
- }
-
- w.Header().Set("Content-Type", "application/json")
-
- clientIP := getClientIP(r)
-
- var req struct {
- Username string `json:"username"`
- }
-
- if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- http.Error(w, `{"error": "Invalid JSON"}`, http.StatusBadRequest)
- return
- }
-
- s.mu.Lock()
-
- if len(req.Username) > s.Config.Options.NameMaxLength {
- http.Error(w, fmt.Sprintf(`{"error": "Username too long (%v out of %v characters maximum)"}`, len(req.Username), s.Config.Options.NameMaxLength), http.StatusRequestEntityTooLarge)
- s.mu.Unlock()
- return
- }
-
- if !validUsername(req.Username) {
- http.Error(w, fmt.Sprintf(`{"error": "Username must only contain alphanumeric characters and/or underscores"}`), http.StatusBadRequest)
- s.mu.Unlock()
- return
- }
-
- if s.Database.UserNameExists(req.Username) {
- http.Error(w, fmt.Sprintf(`{"error": "Username already exists"}`), http.StatusConflict)
- s.mu.Unlock()
- return
- }
-
- if s.Database.UserExists(clientIP) {
- s.Database.UserNameChange(clientIP, req.Username)
- } else {
- s.Database.UserAdd(clientIP, req.Username)
- }
-
- s.mu.Unlock()
-
- json.NewEncoder(w).Encode(map[string]string{"status": "Username registered"})
-}
-
-func getMessageTemplate(filepath string, body string) string {
- contents, _ := os.ReadFile(filepath)
- return strings.Replace(string(contents), "{{body}}", body, 1)
-}
-
-func (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {
- switch r.Method {
- case http.MethodPatch:
- w.Header().Set("Content-Type", "application/json")
- var req struct {
- MessageId string `json:"messageId"`
- MessageContent string `json:"messageContent"`
- }
- if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- http.Error(w, `{"error": "Invalid JSON"}`, http.StatusBadRequest)
- return
- }
-
- clientIP := getClientIP(r)
- if affected, err := s.Database.MessageEditIfOwner(req.MessageId, req.MessageContent, clientIP); err != nil {
- http.Error(w, `{"error": "Unauthorized"}`, http.StatusNotFound)
- return
- } else if affected == 0 {
- http.Error(w, `{"error": "Message not found"}`, http.StatusNotFound)
- return
- }
-
- json.NewEncoder(w).Encode(map[string]string{
- "status": "Message edited successfully",
- })
-
- case http.MethodGet:
- w.Header().Set("Content-Type", "text/html")
-
- var body string
- messages := s.Database.MessagesGet()
- for _, msg := range messages {
- clientIP := getClientIP(r)
- timeZone := s.Database.UserGetTimezone(clientIP)
- timeLocal := TimeStringToTimeInLocation(msg.Timestamp, timeZone)
- body += fmt.Sprintf(`%s
%s
%s
%s
`,
- msg.Id, msg.SenderUsername, timeLocal, msg.Content)
- }
-
- w.Write([]byte(getMessageTemplate(s.Config.Paths.MessagesHtmlPath, body)))
- case http.MethodPut:
- w.Header().Set("Content-Type", "application/json")
-
- // Get client's IP
- clientIP := getClientIP(r)
-
- s.mu.Lock()
- exists := s.Database.UserExists(clientIP)
- username := s.Database.UserNameGet(clientIP)
- s.mu.Unlock()
-
- if !exists {
- errorFmt := fmt.Sprintf(`{"error": "IP %s not registered with username"}`, clientIP)
- http.Error(w, errorFmt, http.StatusUnauthorized)
- return
- }
-
- var msg struct {
- Message string `json:"message"`
- }
-
- if err := json.NewDecoder(r.Body).Decode(&msg); err != nil {
- http.Error(w, `{"error": "Invalid JSON"}`, http.StatusBadRequest)
- return
- }
-
- s.Database.MessageAdd(clientIP, msg.Message)
-
- json.NewEncoder(w).Encode(map[string]string{
- "status": "Message received",
- "from": username,
- })
- case http.MethodDelete:
- w.Header().Set("Content-Type", "application/json")
-
- var req struct {
- MessageId string `json:"messageId"`
- }
-
- if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- http.Error(w, `{"error": "Invalid JSON"}`, http.StatusBadRequest)
- return
- }
-
- clientIP := getClientIP(r)
- if affected, err := s.Database.MessageDeleteIfOwner(req.MessageId, clientIP); err != nil {
- http.Error(w, `{"error": "Unauthorized"}`, http.StatusNotFound)
- return
- } else if affected == 0 {
- http.Error(w, `{"error": "Message not found"}`, http.StatusNotFound)
- return
- }
-
- json.NewEncoder(w).Encode(map[string]string{
- "status": "Message deleted",
- })
- default:
- http.Error(w, `{"error": "Method not allowed"}`, http.StatusMethodNotAllowed)
- return
- }
-}
-
-func (s *Server) handleUsernameStatus(w http.ResponseWriter, r *http.Request) {
- if r.Method != http.MethodGet {
- http.Error(w, `{"error": "Method not allowed"}`, http.StatusMethodNotAllowed)
- return
- }
-
- w.Header().Set("Content-Type", "application/json")
- clientIP := getClientIP(r)
-
- s.mu.Lock()
- exists := s.Database.UserExists(clientIP)
- username := s.Database.UserNameGet(clientIP)
- s.mu.Unlock()
-
- json.NewEncoder(w).Encode(map[string]interface{}{
- "hasUsername": exists,
- "username": username,
- })
-}
-
-func (s *Server) handleUsers(w http.ResponseWriter, r *http.Request) {
- if r.Method != http.MethodGet {
- http.Error(w, `{"error": "Method not allowed"}`, http.StatusMethodNotAllowed)
- return
- }
-
- w.Header().Set("Content-Type", "application/json")
-
- clientIP := getClientIP(r)
- s.updateActivity(clientIP)
- s.cleanupActivity()
- s.mu.Lock()
- var users []string
- for ip := range s.Connected {
- // for all connected, get their usernames
- users = append(users, s.Database.UserNameGet(ip))
- }
- s.mu.Unlock()
-
- json.NewEncoder(w).Encode(map[string]interface{}{
- "users": users,
- })
-}
-
-func (s *Server) handleTimezone(w http.ResponseWriter, r *http.Request) {
- if r.Method != http.MethodPost {
- http.Error(w, `{"error": "Method not allowed"}`, http.StatusMethodNotAllowed)
- return
- }
- w.Header().Set("Content-Type", "application/json")
-
- clientIP := getClientIP(r)
-
- var req struct {
- Timezone string `json:"timezone"`
- }
-
- if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- http.Error(w, `{"error": "Invalid JSON"}`, http.StatusBadRequest)
- return
- }
-
- s.mu.Lock()
-
- if !s.Database.UserExists(clientIP) {
- http.Error(w, `{"error": "User not registered"}`, http.StatusUnauthorized)
- s.mu.Unlock()
- return
- }
-
- s.Database.UserTimezoneSet(clientIP, req.Timezone)
- s.mu.Unlock()
- json.NewEncoder(w).Encode(map[string]string{
- "status": "success",
- })
-}
-
-func (s *Server) handleRoot(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path != "/" {
- http.NotFound(w, r)
- return
- }
- w.Header().Set("Content-Type", "text/html")
- file := readFile(s.Config.Paths.IndexHtmlPath)
- w.Write(file)
-}
-
-func readFile(filepath string) []byte {
- contents, _ := os.ReadFile(filepath)
- return contents
-}
-
-func (s *Server) handleJs(w http.ResponseWriter, r *http.Request) {
- _ = r
- w.Header().Set("Content-Type", "application/javascript")
- file := readFile(s.Config.Paths.IndexJsPath)
- w.Write(file)
-}
-
-func (s *Server) handleCss(w http.ResponseWriter, r *http.Request) {
- _ = r
- w.Header().Set("Content-Type", "text/css")
- file := readFile(s.Config.Paths.IndexCssPath)
- w.Write(file)
-}
-
-func (s *Server) handleMessagesLength(w http.ResponseWriter, r *http.Request) {
- // should return the number of messages in the database
- if r.Method != http.MethodGet {
- http.Error(w, `{"error": "Method not allowed"}`, http.StatusMethodNotAllowed)
- return
- }
- w.Header().Set("Content-Type", "application/json")
- messages := s.Database.MessagesGet()
- json.NewEncoder(w).Encode(map[string]int{
- "length": len(messages),
- })
-}
-
-func (s *Server) Run() {
- s.Database.DbCreateTableMessages()
- s.Database.DbCreateTableUsers()
- s.Database.DeleteOldMessages(s.Config.Options.MessageMaxAge)
- handler := http.NewServeMux()
- handler.HandleFunc("/ping", s.handlePing)
- handler.HandleFunc("/username", s.handleUsername)
- handler.HandleFunc("/messages", s.handleMessages)
- handler.HandleFunc("/messages/length", s.handleMessagesLength)
- handler.HandleFunc("/username/status", s.handleUsernameStatus)
- handler.HandleFunc("/users", s.handleUsers)
- handler.HandleFunc("/", s.handleRoot)
- handler.HandleFunc("/root.js", s.handleJs)
- handler.HandleFunc("/root.css", s.handleCss)
- handler.HandleFunc("/timezone", s.handleTimezone)
- fmt.Printf("Server starting on %s:%d\n", s.Config.Server.IpAddress, s.Config.Server.Port)
- defer s.Stop()
- if err := http.ListenAndServe(fmt.Sprintf("%s:%d", s.Config.Server.IpAddress, s.Config.Server.Port), GzipMiddleware(handler)); err != nil {
- fmt.Printf("Server error: %v\n", err)
- }
-}
-
-func (s *Server) Stop() {
- s.Database.Close()
-}
-
-func main() {
+func init() {
if len(os.Args) < 2 {
fmt.Printf("Usage: %s \n", os.Args[0])
os.Exit(1)
}
- _, err := os.OpenFile(os.Args[1], os.O_RDONLY, 0)
- if err != nil {
- fmt.Println("Error opening config file: ", err)
- os.Exit(1)
- }
- config := LoadConfig(os.Args[1])
+ config = srv.LoadConfig(os.Args[1])
fmt.Println("Config loaded")
- server := NewServer(config)
- server.Run()
}
-type Config struct {
- Server struct {
- IpAddress string `json:"ipAddress"`
- Port int `json:"port"`
- } `json:"server"`
- Paths struct {
- DatabasePath string `json:"databasePath"`
- IndexJsPath string `json:"indexJsPath"`
- IndexCssPath string `json:"indexCssPath"`
- IndexHtmlPath string `json:"indexHtmlPath"`
- MessagesHtmlPath string `json:"messagesHtmlPath"`
- } `json:"paths"`
- Options struct {
- MessageMaxAge int `json:"messageMaxAge"`
- NameMaxLength int `json:"nameMaxLength"`
- } `json:"options"`
-}
-
-func LoadConfig(filepath string) Config {
- contents, _ := os.ReadFile(filepath)
- var config Config
- err := json.Unmarshal(contents, &config)
- config.Paths.IndexHtmlPath = pathMaker(config.Paths.IndexHtmlPath)
- config.Paths.IndexJsPath = pathMaker(config.Paths.IndexJsPath)
- config.Paths.IndexCssPath = pathMaker(config.Paths.IndexCssPath)
- config.Paths.MessagesHtmlPath = pathMaker(config.Paths.MessagesHtmlPath)
- config.Paths.DatabasePath = pathMaker(config.Paths.DatabasePath)
- if err != nil {
- fmt.Println("Error parsing config file: ", err)
- os.Exit(1)
- }
- return config
-}
-
-func pathMaker(path string) string {
- absPath, _ := filepath.Abs(path)
- absPath = filepath.Clean(absPath)
- fmt.Println(absPath)
- return absPath
+func main() {
+ srv.NewServer(config).Run()
}
diff --git a/content/root.html b/public/index.html
similarity index 84%
rename from content/root.html
rename to public/index.html
index 7847664..3d5f672 100644
--- a/content/root.html
+++ b/public/index.html
@@ -29,12 +29,26 @@
+
@@ -58,7 +72,6 @@
-
-