2025-01-22 20:04:47 -06:00
package srv
import (
tu "chat/tu"
"encoding/json"
"fmt"
2025-01-23 19:37:15 -06:00
"math/rand"
2025-01-22 20:04:47 -06:00
"net/http"
)
2025-01-23 19:37:15 -06:00
func generateName ( ) string {
adjectives := [ ] string { "Unrelenting" , "Mystical" , "Radiant" , "Curious" , "Peaceful" , "Ancient" , "Wandering" , "Silent" , "Celestial" , "Dancing" , "Eternal" , "Resolute" , "Whispering" , "Serene" , "Wild" }
colors := [ ] string { "Purple" , "Azure" , "Crimson" , "Golden" , "Emerald" , "Sapphire" , "Obsidian" , "Silver" , "Amber" , "Jade" , "Indigo" , "Violet" , "Cerulean" , "Copper" , "Pearl" }
nouns := [ ] string { "Elephant" , "Phoenix" , "Dragon" , "Warrior" , "Spirit" , "Tiger" , "Raven" , "Mountain" , "River" , "Storm" , "Falcon" , "Wolf" , "Ocean" , "Star" , "Moon" }
return fmt . Sprintf ( "%s-%s-%s" ,
adjectives [ rand . Intn ( len ( adjectives ) ) ] ,
colors [ rand . Intn ( len ( colors ) ) ] ,
nouns [ rand . Intn ( len ( nouns ) ) ] )
}
2025-01-22 20:04:47 -06:00
func ( s * Server ) handlePing ( w http . ResponseWriter , r * http . Request ) {
clientIP := getClientIP ( r )
s . updateActivity ( clientIP )
s . cleanupActivity ( )
w . WriteHeader ( http . StatusOK )
}
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
}
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 )
return
}
if ! validUsername ( req . Username ) {
http . Error ( w , fmt . Sprintf ( ` { "error": "Username must only contain alphanumeric characters and/or underscores"} ` ) , http . StatusBadRequest )
return
}
if s . Database . UserNameExists ( req . Username ) {
http . Error ( w , fmt . Sprintf ( ` { "error": "Username already exists"} ` ) , http . StatusConflict )
return
}
2025-01-23 19:37:15 -06:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
if username , ok := s . LoggedIn [ clientIP ] ; ok {
s . LogUserOut ( username )
s . Database . UserNameChange ( username , req . Username )
s . LogUserIn ( clientIP , req . Username )
json . NewEncoder ( w ) . Encode ( map [ string ] string { "status" : "Username changed" } )
2025-01-22 20:04:47 -06:00
} else {
2025-01-23 19:37:15 -06:00
http . Error ( w , ` { "error": "Failure to change username"} ` , http . StatusUnauthorized )
2025-01-22 20:04:47 -06:00
}
}
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 )
2025-01-23 19:37:15 -06:00
username , ok := s . LoggedIn [ clientIP ]
timeZone := "UTC"
if ok {
timeZone = s . Database . UserGetTimezone ( username )
}
2025-01-22 20:04:47 -06:00
timeLocal := tu . TimeStringToTimeInLocation ( msg . Timestamp , timeZone )
edited := ""
if msg . Edited {
edited = "(edited)"
}
body += fmt . Sprintf ( ` <p>%s<br><span class="username">%s</span><br><span class="timestamp">%s %s</span><br><span class="message">%s</span><br></p> ` ,
msg . Id , msg . SenderUsername , timeLocal , edited , msg . Content )
}
w . Write ( [ ] byte ( getMessageTemplate ( body ) ) )
case http . MethodPut :
w . Header ( ) . Set ( "Content-Type" , "application/json" )
var msg struct {
2025-01-23 19:37:15 -06:00
Username string ` json:"username" `
Message string ` json:"message" `
2025-01-22 20:04:47 -06:00
}
if err := json . NewDecoder ( r . Body ) . Decode ( & msg ) ; err != nil {
http . Error ( w , ` { "error": "Invalid JSON"} ` , http . StatusBadRequest )
return
}
2025-01-23 19:37:15 -06:00
clientIP := getClientIP ( r )
if username , ok := s . LoggedIn [ clientIP ] ; ok {
s . mu . Lock ( )
defer s . mu . Unlock ( )
s . Database . MessageAdd ( username , msg . Message )
json . NewEncoder ( w ) . Encode ( map [ string ] string {
"status" : "Message received" ,
"from" : username ,
} )
} else {
http . Error ( w , ` { "error": "Unauthorized"} ` , http . StatusUnauthorized )
}
2025-01-22 20:04:47 -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" ,
} )
default :
http . Error ( w , ` { "error": "Method not allowed"} ` , http . StatusMethodNotAllowed )
return
}
}
func ( s * Server ) handleUsernameStatus ( w http . ResponseWriter , r * http . Request ) {
2025-01-23 19:37:15 -06:00
// if r.Method != http.MethodGet {
// http.Error(w, `{"error": "Method not allowed"}`, http.StatusMethodNotAllowed)
// return
// }
2025-01-22 20:04:47 -06:00
clientIP := getClientIP ( r )
2025-01-23 19:37:15 -06:00
username , ok := s . LoggedIn [ clientIP ]
2025-01-22 20:04:47 -06:00
2025-01-23 19:37:15 -06:00
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2025-01-22 20:04:47 -06:00
json . NewEncoder ( w ) . Encode ( map [ string ] interface { } {
2025-01-23 19:37:15 -06:00
"hasUsername" : ok ,
2025-01-22 20:04:47 -06:00
"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 ( )
2025-01-23 19:37:15 -06:00
defer s . mu . Unlock ( )
2025-01-22 20:04:47 -06:00
var users [ ] string
for ip := range s . Connected {
// for all connected, get their usernames
2025-01-23 19:37:15 -06:00
if username , ok := s . LoggedIn [ ip ] ; ok {
users = append ( users , username )
}
2025-01-22 20:04:47 -06:00
}
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 ( )
2025-01-23 19:37:15 -06:00
defer s . mu . Unlock ( )
if username , ok := s . LoggedIn [ clientIP ] ; ok {
s . Database . UserTimezoneSet ( username , req . Timezone )
json . NewEncoder ( w ) . Encode ( map [ string ] string {
"status" : "success" ,
} )
} else {
2025-01-22 20:04:47 -06:00
http . Error ( w , ` { "error": "User not registered"} ` , http . StatusUnauthorized )
}
}
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 ( 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 ) ,
} )
}
2025-01-23 19:37:15 -06:00
func ( s * Server ) handleLogin ( w http . ResponseWriter , r * http . Request ) {
switch r . Method {
case http . MethodGet :
w . Header ( ) . Set ( "Content-Type" , "text/html" )
file := readFile ( s . Config . Paths . LoginHtmlPath )
w . Write ( file )
return
case http . MethodPost :
w . Header ( ) . Set ( "Content-Type" , "application/json" )
var req struct {
Username string ` json:"username" `
Password string ` json:"password" `
}
if err := json . NewDecoder ( r . Body ) . Decode ( & req ) ; err != nil {
http . Error ( w , ` { "error": "Invalid JSON"} ` , http . StatusBadRequest )
return
}
validLogin := s . Database . UserPasswordCheck ( req . Username , req . Password )
if ! validLogin {
http . Error ( w , ` { "error": "Invalid username or password"} ` , http . StatusUnauthorized )
return
} else {
clientIP := getClientIP ( r )
s . LogUserIn ( clientIP , req . Username )
json . NewEncoder ( w ) . Encode ( map [ string ] string {
"status" : "Logged in" ,
} )
}
}
}
func ( s * Server ) handleSignup ( w http . ResponseWriter , r * http . Request ) {
switch r . Method {
case http . MethodGet :
w . Header ( ) . Set ( "Content-Type" , "text/html" )
file := readFile ( s . Config . Paths . SignupHtmlPath )
w . Write ( file )
return
case http . MethodPost :
w . Header ( ) . Set ( "Content-Type" , "application/json" )
var req struct {
Username string ` json:"username" `
Password string ` json:"password" `
}
if err := json . NewDecoder ( r . Body ) . Decode ( & req ) ; err != nil {
http . Error ( w , ` { "error": "Invalid JSON"} ` , http . StatusBadRequest )
return
}
// validate username length
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 )
return
}
// validate username
if ! validUsername ( req . Username ) {
http . Error ( w , fmt . Sprintf ( ` { "error": "Username must only contain alphanumeric characters and/or underscores"} ` ) , http . StatusBadRequest )
return
}
// validate user doesnt already exist
if s . Database . UserNameExists ( req . Username ) {
http . Error ( w , fmt . Sprintf ( ` { "error": "Username already exists"} ` ) , http . StatusConflict )
return
}
// add user to database with hashedpassword
err := s . Database . UserAddWithPassword ( req . Username , req . Password )
if err != nil {
fmt . Println ( "Database error while signing up a new user" )
http . Error ( w , fmt . Sprintf ( ` { "error": "%v"} ` , err ) , http . StatusInternalServerError )
return
}
// log user in
clientIP := getClientIP ( r )
s . LogUserIn ( clientIP , req . Username )
json . NewEncoder ( w ) . Encode ( map [ string ] string {
"status" : "User created" ,
} )
}
}