2025-01-18 22:01:02 -06:00
package main
import (
"compress/gzip"
"database/sql"
"encoding/json"
"fmt"
_ "github.com/mattn/go-sqlite3"
"io"
"net"
"net/http"
"os"
2025-01-20 12:23:27 -06:00
"path/filepath"
2025-01-18 22:01:02 -06:00
"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 ) {
2025-01-20 12:23:27 -06:00
_ , err := db . db . Exec ( "UPDATE users SET timezone = ? WHERE ip_address = ?" , timezone , ip_address )
2025-01-18 22:01:02 -06:00
if err != nil {
fmt . Println ( err )
}
}
func ( db * Database ) UserAdd ( ip_address , username string ) {
2025-01-20 12:23:27 -06:00
_ , err := db . db . Exec ( "INSERT INTO users (username, ip_address) VALUES (?, ?)" , username , ip_address )
2025-01-18 22:01:02 -06:00
if err != nil {
fmt . Println ( err )
}
}
func ( db * Database ) MessageAdd ( ip_address string , content string ) {
2025-01-20 12:23:27 -06:00
_ , err := db . db . Exec ( "INSERT INTO messages (ip_address, content) VALUES (?, ?)" , ip_address , content )
2025-01-18 22:01:02 -06:00
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
}
2025-01-20 12:23:27 -06:00
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
}
2025-01-18 22:01:02 -06:00
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 )
2025-01-20 12:23:27 -06:00
2025-01-18 22:01:02 -06:00
message := Message {
Id : id ,
Content : content ,
SenderIp : ip_address ,
SenderUsername : username ,
Timestamp : created_at ,
}
2025-01-20 12:23:27 -06:00
2025-01-18 22:01:02 -06:00
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 ) {
2025-01-20 12:23:27 -06:00
_ , 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 )
2025-01-18 22:01:02 -06:00
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
}
2025-01-20 12:23:27 -06:00
func ( db * Database ) MessageDeleteId ( id string ) {
_ , err := db . db . Exec ( "DELETE FROM messages WHERE id = ?" , id )
if err != nil {
fmt . Println ( err )
}
}
2025-01-20 19:16:35 -06:00
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
}
2025-01-20 12:23:27 -06:00
2025-01-21 16:14:22 -06:00
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
}
2025-01-20 12:23:27 -06:00
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 )
2025-01-18 22:01:02 -06:00
if err != nil {
fmt . Println ( err )
}
}
2025-01-20 12:23:27 -06:00
func ( db * Database ) UserDeleteIp ( ip string ) {
_ , err := db . db . Exec ( "DELETE FROM users WHERE ip_address = ?" , ip )
2025-01-18 22:01:02 -06:00
if err != nil {
fmt . Println ( err )
}
}
func ( db * Database ) UsersDelete ( ) {
2025-01-20 12:23:27 -06:00
_ , err := db . db . Exec ( "DELETE FROM users" )
2025-01-18 22:01:02 -06:00
if err != nil {
fmt . Println ( err )
}
}
func ( db * Database ) MessagesDelete ( ) {
2025-01-20 12:23:27 -06:00
_ , err := db . db . Exec ( "DELETE FROM messages" )
2025-01-18 22:01:02 -06:00
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
2025-01-20 12:23:27 -06:00
Config Config
2025-01-18 22:01:02 -06:00
mu sync . Mutex // For thread safety
}
2025-01-20 12:23:27 -06:00
func NewServer ( config Config ) * Server {
2025-01-18 22:01:02 -06:00
return & Server {
Connected : make ( map [ string ] time . Time ) ,
2025-01-20 12:23:27 -06:00
Database : OpenDatabase ( config . Paths . DatabasePath ) ,
Config : config ,
2025-01-18 22:01:02 -06:00
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 ( )
2025-01-21 11:26:56 -06:00
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 )
2025-01-18 22:01:02 -06:00
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 )
2025-01-21 11:26:56 -06:00
s . mu . Unlock ( )
2025-01-18 22:01:02 -06:00
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" } )
}
2025-01-20 12:23:27 -06:00
func getMessageTemplate ( filepath string , body string ) string {
contents , _ := os . ReadFile ( filepath )
2025-01-18 22:01:02 -06:00
return strings . Replace ( string ( contents ) , "{{body}}" , body , 1 )
}
func ( s * Server ) handleMessages ( w http . ResponseWriter , r * http . Request ) {
switch r . Method {
2025-01-21 16:14:22 -06:00
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" ,
} )
2025-01-18 22:01:02 -06:00
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 )
2025-01-20 19:16:35 -06:00
body += fmt . Sprintf ( ` <p>%s<br><span class="username">%s</span><br><span class="timestamp">%s</span><br><span class="message">%s</span></p> ` ,
2025-01-20 12:23:27 -06:00
msg . Id , msg . SenderUsername , timeLocal , msg . Content )
2025-01-18 22:01:02 -06:00
}
2025-01-20 12:23:27 -06:00
w . Write ( [ ] byte ( getMessageTemplate ( s . Config . Paths . MessagesHtmlPath , body ) ) )
2025-01-18 22:01:02 -06:00
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 ,
} )
2025-01-20 19:16:35 -06:00
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" ,
} )
2025-01-18 22:01:02 -06:00
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" )
2025-01-20 12:23:27 -06:00
file := readFile ( s . Config . Paths . IndexHtmlPath )
w . Write ( file )
2025-01-18 22:01:02 -06:00
}
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" )
2025-01-20 12:23:27 -06:00
file := readFile ( s . Config . Paths . IndexJsPath )
w . Write ( file )
2025-01-18 22:01:02 -06:00
}
func ( s * Server ) handleCss ( w http . ResponseWriter , r * http . Request ) {
_ = r
w . Header ( ) . Set ( "Content-Type" , "text/css" )
2025-01-20 12:23:27 -06:00
file := readFile ( s . Config . Paths . IndexCssPath )
w . Write ( file )
2025-01-18 22:01:02 -06:00
}
2025-01-21 12:25:04 -06:00
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 ) ,
} )
}
2025-01-18 22:01:02 -06:00
func ( s * Server ) Run ( ) {
2025-01-20 12:23:27 -06:00
s . Database . DbCreateTableMessages ( )
s . Database . DbCreateTableUsers ( )
2025-01-21 11:26:56 -06:00
s . Database . DeleteOldMessages ( s . Config . Options . MessageMaxAge )
2025-01-18 22:01:02 -06:00
handler := http . NewServeMux ( )
handler . HandleFunc ( "/ping" , s . handlePing )
handler . HandleFunc ( "/username" , s . handleUsername )
handler . HandleFunc ( "/messages" , s . handleMessages )
2025-01-21 12:25:04 -06:00
handler . HandleFunc ( "/messages/length" , s . handleMessagesLength )
2025-01-18 22:01:02 -06:00
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 )
2025-01-20 12:23:27 -06:00
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 {
2025-01-18 22:01:02 -06:00
fmt . Printf ( "Server error: %v\n" , err )
}
}
func ( s * Server ) Stop ( ) {
s . Database . Close ( )
}
func main ( ) {
2025-01-20 12:23:27 -06:00
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 )
2025-01-18 22:01:02 -06:00
server . Run ( )
}
2025-01-20 12:23:27 -06:00
type Config struct {
Server struct {
2025-01-21 11:26:56 -06:00
IpAddress string ` json:"ipAddress" `
Port int ` json:"port" `
2025-01-20 12:23:27 -06:00
} ` 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" `
2025-01-21 11:26:56 -06:00
Options struct {
MessageMaxAge int ` json:"messageMaxAge" `
NameMaxLength int ` json:"nameMaxLength" `
} ` json:"options" `
2025-01-20 12:23:27 -06:00
}
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
}