changes
This commit is contained in:
parent
5420fbb7a9
commit
2428f69df8
14
config.json
Normal file
14
config.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"server": {
|
||||||
|
"ipAddress": "192.168.1.222",
|
||||||
|
"port": 8080,
|
||||||
|
"messageMaxAge": 259200
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"databasePath": "/home/radon/Documents/chattest.db",
|
||||||
|
"indexJsPath": "./content/root.js",
|
||||||
|
"indexCssPath": "./content/root.css",
|
||||||
|
"indexHtmlPath": "./content/root.html",
|
||||||
|
"messagesHtmlPath": "./content/messages.html"
|
||||||
|
}
|
||||||
|
}
|
@ -149,8 +149,7 @@ async function loadMessages() {
|
|||||||
url,
|
url,
|
||||||
);
|
);
|
||||||
if (videoId) {
|
if (videoId) {
|
||||||
return `<a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>
|
return `<div class="youtube-embed">
|
||||||
<div class="youtube-embed">
|
|
||||||
<iframe
|
<iframe
|
||||||
width="100%"
|
width="100%"
|
||||||
height="315"
|
height="315"
|
||||||
@ -165,8 +164,7 @@ async function loadMessages() {
|
|||||||
"Attempting to embed image:",
|
"Attempting to embed image:",
|
||||||
url,
|
url,
|
||||||
);
|
);
|
||||||
return `<a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>
|
return `<div class="image-embed">
|
||||||
<div class="image-embed">
|
|
||||||
<img
|
<img
|
||||||
src="${url}"
|
src="${url}"
|
||||||
alt="Embedded image"
|
alt="Embedded image"
|
176
main.go
176
main.go
@ -10,6 +10,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -77,27 +78,24 @@ func (db *Database) DbCreateTableUsers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) UserTimezoneSet(ip_address, timezone string) {
|
func (db *Database) UserTimezoneSet(ip_address, timezone string) {
|
||||||
stmt, err := db.db.Prepare("UPDATE users SET timezone = ? WHERE ip_address = ?")
|
_, err := db.db.Exec("UPDATE users SET timezone = ? WHERE ip_address = ?", timezone, ip_address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
stmt.Exec(timezone, ip_address)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) UserAdd(ip_address, username string) {
|
func (db *Database) UserAdd(ip_address, username string) {
|
||||||
stmt, err := db.db.Prepare("INSERT INTO users (username, ip_address) VALUES (?, ?)")
|
_, err := db.db.Exec("INSERT INTO users (username, ip_address) VALUES (?, ?)", username, ip_address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
stmt.Exec(username, ip_address)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) MessageAdd(ip_address string, content string) {
|
func (db *Database) MessageAdd(ip_address string, content string) {
|
||||||
stmt, err := db.db.Prepare("INSERT INTO messages (ip_address, content) VALUES (?, ?)")
|
_, err := db.db.Exec("INSERT INTO messages (ip_address, content) VALUES (?, ?)", ip_address, content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
stmt.Exec(ip_address, content)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) UserNameGet(ip_address string) string {
|
func (db *Database) UserNameGet(ip_address string) string {
|
||||||
@ -112,6 +110,18 @@ func (db *Database) UserNameGet(ip_address string) string {
|
|||||||
return 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 {
|
func (db *Database) UserGetTimezone(ip_address string) string {
|
||||||
rows, err := db.db.Query("SELECT timezone FROM users WHERE ip_address = ?", ip_address)
|
rows, err := db.db.Query("SELECT timezone FROM users WHERE ip_address = ?", ip_address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -171,6 +181,27 @@ func (db *Database) MessagesGet() []Message {
|
|||||||
var created_at string
|
var created_at string
|
||||||
var username string
|
var username string
|
||||||
rows.Scan(&id, &ip_address, &content, &created_at, &username)
|
rows.Scan(&id, &ip_address, &content, &created_at, &username)
|
||||||
|
|
||||||
|
// TODO: Implement better message parsing for admin commands
|
||||||
|
// TESTING:
|
||||||
|
// if msgId, ok := strings.CutPrefix(content, "@delete message id"); ok {
|
||||||
|
// msgId = strings.TrimSpace(msgId)
|
||||||
|
// go func() {
|
||||||
|
// db.MessageDeleteId(msgId)
|
||||||
|
// // db.MessageDeleteId(id)
|
||||||
|
// }()
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// TESTING:
|
||||||
|
// if msgId, ok := strings.CutPrefix(content, "@delete user messages"); ok {
|
||||||
|
// msgId = strings.TrimSpace(msgId)
|
||||||
|
// go func() {
|
||||||
|
// db.UserMessagesDelete(ip_address)
|
||||||
|
// // db.MessageDeleteId(id)
|
||||||
|
// }()
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
|
||||||
message := Message{
|
message := Message{
|
||||||
Id: id,
|
Id: id,
|
||||||
Content: content,
|
Content: content,
|
||||||
@ -178,6 +209,7 @@ func (db *Database) MessagesGet() []Message {
|
|||||||
SenderUsername: username,
|
SenderUsername: username,
|
||||||
Timestamp: created_at,
|
Timestamp: created_at,
|
||||||
}
|
}
|
||||||
|
|
||||||
messages = append(messages, message)
|
messages = append(messages, message)
|
||||||
}
|
}
|
||||||
return messages
|
return messages
|
||||||
@ -202,11 +234,17 @@ func (db *Database) UserExists(ip string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) UserNameChange(ip, newUsername string) {
|
func (db *Database) UserNameChange(ip, newUsername string) {
|
||||||
stmt, err := db.db.Prepare("UPDATE users SET username = ? WHERE ip_address = ?")
|
_, 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 {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
stmt.Exec(newUsername, ip)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) UserMessagesGet(ip string) []Message {
|
func (db *Database) UserMessagesGet(ip string) []Message {
|
||||||
@ -244,36 +282,43 @@ func (db *Database) UserMessagesGet(ip string) []Message {
|
|||||||
return messages
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) MessageDelete(id string) {
|
func (db *Database) MessageDeleteId(id string) {
|
||||||
stmt, err := db.db.Prepare("DELETE FROM messages WHERE id = ?")
|
_, err := db.db.Exec("DELETE FROM messages WHERE id = ?", id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
stmt.Exec(id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) UserDelete(ip string) {
|
func (db *Database) DeleteOldMessages(ageMinutes int) {
|
||||||
stmt, err := db.db.Prepare("DELETE FROM users WHERE ip_address = ?")
|
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 {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
stmt.Exec(ip)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) UsersDelete() {
|
func (db *Database) UsersDelete() {
|
||||||
stmt, err := db.db.Prepare("DELETE FROM users")
|
_, err := db.db.Exec("DELETE FROM users")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
stmt.Exec()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) MessagesDelete() {
|
func (db *Database) MessagesDelete() {
|
||||||
stmt, err := db.db.Prepare("DELETE FROM messages")
|
_, err := db.db.Exec("DELETE FROM messages")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
stmt.Exec()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type gzipResponseWriter struct {
|
type gzipResponseWriter struct {
|
||||||
@ -328,19 +373,18 @@ type Message struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Ip string
|
|
||||||
Port int
|
|
||||||
Connected map[string]time.Time // Map IP -> Last activity time
|
Connected map[string]time.Time // Map IP -> Last activity time
|
||||||
Database *Database
|
Database *Database
|
||||||
|
Config Config
|
||||||
mu sync.Mutex // For thread safety
|
mu sync.Mutex // For thread safety
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(ip string, port int, dbpath string) *Server {
|
func NewServer(config Config) *Server {
|
||||||
|
|
||||||
return &Server{
|
return &Server{
|
||||||
Ip: ip,
|
|
||||||
Port: port,
|
|
||||||
Connected: make(map[string]time.Time),
|
Connected: make(map[string]time.Time),
|
||||||
Database: OpenDatabase(dbpath),
|
Database: OpenDatabase(config.Paths.DatabasePath),
|
||||||
|
Config: config,
|
||||||
mu: sync.Mutex{},
|
mu: sync.Mutex{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -431,8 +475,8 @@ func (s *Server) handleUsername(w http.ResponseWriter, r *http.Request) {
|
|||||||
json.NewEncoder(w).Encode(map[string]string{"status": "Username registered"})
|
json.NewEncoder(w).Encode(map[string]string{"status": "Username registered"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMessageTemplate(file string, body string) string {
|
func getMessageTemplate(filepath string, body string) string {
|
||||||
contents, _ := os.ReadFile(file)
|
contents, _ := os.ReadFile(filepath)
|
||||||
return strings.Replace(string(contents), "{{body}}", body, 1)
|
return strings.Replace(string(contents), "{{body}}", body, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,11 +491,11 @@ func (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {
|
|||||||
clientIP := getClientIP(r)
|
clientIP := getClientIP(r)
|
||||||
timeZone := s.Database.UserGetTimezone(clientIP)
|
timeZone := s.Database.UserGetTimezone(clientIP)
|
||||||
timeLocal := TimeStringToTimeInLocation(msg.Timestamp, timeZone)
|
timeLocal := TimeStringToTimeInLocation(msg.Timestamp, timeZone)
|
||||||
body += fmt.Sprintf(`<p><span class="username">%s </span><span class="timestamp">%s</span><br><span class="message">%s</span></p>`,
|
body += fmt.Sprintf(`%s<p><span class="username">%s </span><span class="timestamp">%s</span><br><span class="message">%s</span></p>`,
|
||||||
msg.SenderUsername, timeLocal, msg.Content)
|
msg.Id, msg.SenderUsername, timeLocal, msg.Content)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Write([]byte(getMessageTemplate("messages.html", body)))
|
w.Write([]byte(getMessageTemplate(s.Config.Paths.MessagesHtmlPath, body)))
|
||||||
|
|
||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
@ -574,7 +618,8 @@ func (s *Server) handleRoot(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
w.Write(readFile("root.html"))
|
file := readFile(s.Config.Paths.IndexHtmlPath)
|
||||||
|
w.Write(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
func readFile(filepath string) []byte {
|
func readFile(filepath string) []byte {
|
||||||
@ -585,16 +630,21 @@ func readFile(filepath string) []byte {
|
|||||||
func (s *Server) handleJs(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleJs(w http.ResponseWriter, r *http.Request) {
|
||||||
_ = r
|
_ = r
|
||||||
w.Header().Set("Content-Type", "application/javascript")
|
w.Header().Set("Content-Type", "application/javascript")
|
||||||
w.Write(readFile("root.js"))
|
file := readFile(s.Config.Paths.IndexJsPath)
|
||||||
|
w.Write(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleCss(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleCss(w http.ResponseWriter, r *http.Request) {
|
||||||
_ = r
|
_ = r
|
||||||
w.Header().Set("Content-Type", "text/css")
|
w.Header().Set("Content-Type", "text/css")
|
||||||
w.Write(readFile("root.css"))
|
file := readFile(s.Config.Paths.IndexCssPath)
|
||||||
|
w.Write(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Run() {
|
func (s *Server) Run() {
|
||||||
|
s.Database.DbCreateTableMessages()
|
||||||
|
s.Database.DbCreateTableUsers()
|
||||||
|
s.Database.DeleteOldMessages(s.Config.Server.MessageMaxAge)
|
||||||
handler := http.NewServeMux()
|
handler := http.NewServeMux()
|
||||||
handler.HandleFunc("/ping", s.handlePing)
|
handler.HandleFunc("/ping", s.handlePing)
|
||||||
handler.HandleFunc("/username", s.handleUsername)
|
handler.HandleFunc("/username", s.handleUsername)
|
||||||
@ -605,8 +655,9 @@ func (s *Server) Run() {
|
|||||||
handler.HandleFunc("/root.js", s.handleJs)
|
handler.HandleFunc("/root.js", s.handleJs)
|
||||||
handler.HandleFunc("/root.css", s.handleCss)
|
handler.HandleFunc("/root.css", s.handleCss)
|
||||||
handler.HandleFunc("/timezone", s.handleTimezone)
|
handler.HandleFunc("/timezone", s.handleTimezone)
|
||||||
fmt.Printf("Server starting on %s:%d\n", s.Ip, s.Port)
|
fmt.Printf("Server starting on %s:%d\n", s.Config.Server.IpAddress, s.Config.Server.Port)
|
||||||
if err := http.ListenAndServe(fmt.Sprintf("%s:%d", s.Ip, s.Port), GzipMiddleware(handler)); err != nil {
|
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)
|
fmt.Printf("Server error: %v\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -616,12 +667,55 @@ func (s *Server) Stop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ip := os.Args[1]
|
if len(os.Args) < 2 {
|
||||||
port, _ := strconv.Atoi(os.Args[2])
|
fmt.Printf("Usage: %s <config.json>\n", os.Args[0])
|
||||||
databaseFile := os.Args[3]
|
os.Exit(1)
|
||||||
server := NewServer(ip, port, databaseFile)
|
}
|
||||||
server.Database.DbCreateTableMessages()
|
_, err := os.OpenFile(os.Args[1], os.O_RDONLY, 0)
|
||||||
server.Database.DbCreateTableUsers()
|
if err != nil {
|
||||||
defer server.Stop()
|
fmt.Println("Error opening config file: ", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
config := LoadConfig(os.Args[1])
|
||||||
|
fmt.Println("Config loaded")
|
||||||
|
server := NewServer(config)
|
||||||
server.Run()
|
server.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Server struct {
|
||||||
|
IpAddress string `json:"ipAddress"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
MessageMaxAge int `json:"messageMaxAge"`
|
||||||
|
} `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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
10
readme.md
10
readme.md
@ -1,19 +1,17 @@
|
|||||||
# Changes To Make
|
# Changes To Make
|
||||||
## Frontend
|
## Frontend
|
||||||
### High Priority
|
### High Priority
|
||||||
- Nothing yet
|
- Change light mode scroll down arrow svg fill color? to the buttom inactive fg color
|
||||||
### Mid Priority
|
### Mid Priority
|
||||||
- Nothing yet
|
- Up arrow in textarea should bring up last input
|
||||||
### Low Priority
|
### Low Priority
|
||||||
- Mobile formatting @media
|
- Mobile formatting @media
|
||||||
- First click of user list does not register
|
- First click of user list does not register
|
||||||
## Backend
|
## Backend
|
||||||
### High Priority
|
### High Priority
|
||||||
- Updating messages should lazily load prior messages
|
- Updating messages should lazily load prior messages (pagination?)
|
||||||
- Separate all configuration out to a config file
|
|
||||||
### Mid Priority
|
### Mid Priority
|
||||||
- Nothing yet
|
- Nothing yet
|
||||||
### Low Priority
|
### Low Priority
|
||||||
- Search functionality?
|
- Search functionality?
|
||||||
- Delete messages? (maybe later)
|
- Delete messages? (in progress, capability exists, flesh this out a bit)
|
||||||
- Old messages should be deleted? Or if database is over a certain size? Not sure if this is really necessary.
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user