790 lines
20 KiB
Go
790 lines
20 KiB
Go
package main
|
|
|
|
import (
|
|
"compress/gzip"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
_ "github.com/mattn/go-sqlite3"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type Database struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
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(`<p>%s<br><span class="username">%s</span><br><span class="timestamp">%s</span><br><span class="message">%s</span></p>`,
|
|
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() {
|
|
if len(os.Args) < 2 {
|
|
fmt.Printf("Usage: %s <config.json>\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])
|
|
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
|
|
}
|