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,31 +149,29 @@ async function loadMessages() {
|
||||
url,
|
||||
);
|
||||
if (videoId) {
|
||||
return `<a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>
|
||||
<div class="youtube-embed">
|
||||
<iframe
|
||||
width="100%"
|
||||
height="315"
|
||||
src="https://www.youtube.com/embed/${videoId}"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen>
|
||||
</iframe>
|
||||
</div>`;
|
||||
return `<div class="youtube-embed">
|
||||
<iframe
|
||||
width="100%"
|
||||
height="315"
|
||||
src="https://www.youtube.com/embed/${videoId}"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen>
|
||||
</iframe>
|
||||
</div>`;
|
||||
} else if (isImageUrl(url)) {
|
||||
console.log(
|
||||
"Attempting to embed image:",
|
||||
url,
|
||||
);
|
||||
return `<a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>
|
||||
<div class="image-embed">
|
||||
<img
|
||||
src="${url}"
|
||||
alt="Embedded image"
|
||||
loading="lazy"
|
||||
onerror="console.log('Image failed to load:', this.src); this.style.display='none'"
|
||||
onload="console.log('Image loaded successfully:', this.src)">
|
||||
</div>`;
|
||||
return `<div class="image-embed">
|
||||
<img
|
||||
src="${url}"
|
||||
alt="Embedded image"
|
||||
loading="lazy"
|
||||
onerror="console.log('Image failed to load:', this.src); this.style.display='none'"
|
||||
onload="console.log('Image loaded successfully:', this.src)">
|
||||
</div>`;
|
||||
}
|
||||
return `<a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>`;
|
||||
},
|
176
main.go
176
main.go
@ -10,6 +10,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -77,27 +78,24 @@ func (db *Database) DbCreateTableUsers() {
|
||||
}
|
||||
|
||||
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 {
|
||||
fmt.Println(err)
|
||||
}
|
||||
stmt.Exec(timezone, ip_address)
|
||||
}
|
||||
|
||||
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 {
|
||||
fmt.Println(err)
|
||||
}
|
||||
stmt.Exec(username, ip_address)
|
||||
}
|
||||
|
||||
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 {
|
||||
fmt.Println(err)
|
||||
}
|
||||
stmt.Exec(ip_address, content)
|
||||
}
|
||||
|
||||
func (db *Database) UserNameGet(ip_address string) string {
|
||||
@ -112,6 +110,18 @@ func (db *Database) UserNameGet(ip_address string) string {
|
||||
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 {
|
||||
@ -171,6 +181,27 @@ func (db *Database) MessagesGet() []Message {
|
||||
var created_at string
|
||||
var username string
|
||||
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{
|
||||
Id: id,
|
||||
Content: content,
|
||||
@ -178,6 +209,7 @@ func (db *Database) MessagesGet() []Message {
|
||||
SenderUsername: username,
|
||||
Timestamp: created_at,
|
||||
}
|
||||
|
||||
messages = append(messages, message)
|
||||
}
|
||||
return messages
|
||||
@ -202,11 +234,17 @@ func (db *Database) UserExists(ip string) bool {
|
||||
}
|
||||
|
||||
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 {
|
||||
fmt.Println(err)
|
||||
}
|
||||
stmt.Exec(newUsername, ip)
|
||||
}
|
||||
|
||||
func (db *Database) UserMessagesGet(ip string) []Message {
|
||||
@ -244,36 +282,43 @@ func (db *Database) UserMessagesGet(ip string) []Message {
|
||||
return messages
|
||||
}
|
||||
|
||||
func (db *Database) MessageDelete(id string) {
|
||||
stmt, err := db.db.Prepare("DELETE FROM messages WHERE id = ?")
|
||||
func (db *Database) MessageDeleteId(id string) {
|
||||
_, err := db.db.Exec("DELETE FROM messages WHERE id = ?", id)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
stmt.Exec(id)
|
||||
}
|
||||
|
||||
func (db *Database) UserDelete(ip string) {
|
||||
stmt, err := db.db.Prepare("DELETE FROM users WHERE ip_address = ?")
|
||||
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)
|
||||
}
|
||||
stmt.Exec(ip)
|
||||
}
|
||||
|
||||
func (db *Database) UsersDelete() {
|
||||
stmt, err := db.db.Prepare("DELETE FROM users")
|
||||
_, err := db.db.Exec("DELETE FROM users")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
stmt.Exec()
|
||||
}
|
||||
|
||||
func (db *Database) MessagesDelete() {
|
||||
stmt, err := db.db.Prepare("DELETE FROM messages")
|
||||
_, err := db.db.Exec("DELETE FROM messages")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
stmt.Exec()
|
||||
}
|
||||
|
||||
type gzipResponseWriter struct {
|
||||
@ -328,19 +373,18 @@ type Message struct {
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Ip string
|
||||
Port int
|
||||
Connected map[string]time.Time // Map IP -> Last activity time
|
||||
Database *Database
|
||||
Config Config
|
||||
mu sync.Mutex // For thread safety
|
||||
}
|
||||
|
||||
func NewServer(ip string, port int, dbpath string) *Server {
|
||||
func NewServer(config Config) *Server {
|
||||
|
||||
return &Server{
|
||||
Ip: ip,
|
||||
Port: port,
|
||||
Connected: make(map[string]time.Time),
|
||||
Database: OpenDatabase(dbpath),
|
||||
Database: OpenDatabase(config.Paths.DatabasePath),
|
||||
Config: config,
|
||||
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"})
|
||||
}
|
||||
|
||||
func getMessageTemplate(file string, body string) string {
|
||||
contents, _ := os.ReadFile(file)
|
||||
func getMessageTemplate(filepath string, body string) string {
|
||||
contents, _ := os.ReadFile(filepath)
|
||||
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)
|
||||
timeZone := s.Database.UserGetTimezone(clientIP)
|
||||
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>`,
|
||||
msg.SenderUsername, timeLocal, msg.Content)
|
||||
body += fmt.Sprintf(`%s<p><span class="username">%s </span><span class="timestamp">%s</span><br><span class="message">%s</span></p>`,
|
||||
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:
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@ -574,7 +618,8 @@ func (s *Server) handleRoot(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
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 {
|
||||
@ -585,16 +630,21 @@ func readFile(filepath string) []byte {
|
||||
func (s *Server) handleJs(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r
|
||||
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) {
|
||||
_ = r
|
||||
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() {
|
||||
s.Database.DbCreateTableMessages()
|
||||
s.Database.DbCreateTableUsers()
|
||||
s.Database.DeleteOldMessages(s.Config.Server.MessageMaxAge)
|
||||
handler := http.NewServeMux()
|
||||
handler.HandleFunc("/ping", s.handlePing)
|
||||
handler.HandleFunc("/username", s.handleUsername)
|
||||
@ -605,8 +655,9 @@ func (s *Server) Run() {
|
||||
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.Ip, s.Port)
|
||||
if err := http.ListenAndServe(fmt.Sprintf("%s:%d", s.Ip, s.Port), GzipMiddleware(handler)); err != nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -616,12 +667,55 @@ func (s *Server) Stop() {
|
||||
}
|
||||
|
||||
func main() {
|
||||
ip := os.Args[1]
|
||||
port, _ := strconv.Atoi(os.Args[2])
|
||||
databaseFile := os.Args[3]
|
||||
server := NewServer(ip, port, databaseFile)
|
||||
server.Database.DbCreateTableMessages()
|
||||
server.Database.DbCreateTableUsers()
|
||||
defer server.Stop()
|
||||
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"`
|
||||
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
|
||||
## Frontend
|
||||
### High Priority
|
||||
- Nothing yet
|
||||
- Change light mode scroll down arrow svg fill color? to the buttom inactive fg color
|
||||
### Mid Priority
|
||||
- Nothing yet
|
||||
- Up arrow in textarea should bring up last input
|
||||
### Low Priority
|
||||
- Mobile formatting @media
|
||||
- First click of user list does not register
|
||||
## Backend
|
||||
### High Priority
|
||||
- Updating messages should lazily load prior messages
|
||||
- Separate all configuration out to a config file
|
||||
- Updating messages should lazily load prior messages (pagination?)
|
||||
### Mid Priority
|
||||
- Nothing yet
|
||||
### Low Priority
|
||||
- Search functionality?
|
||||
- Delete messages? (maybe later)
|
||||
- Old messages should be deleted? Or if database is over a certain size? Not sure if this is really necessary.
|
||||
- Delete messages? (in progress, capability exists, flesh this out a bit)
|
||||
|
Loading…
x
Reference in New Issue
Block a user