package srv
import (
tu "chat/tu"
"encoding/json"
"fmt"
"math/rand"
"net/http"
)
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))])
}
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
}
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"})
} else {
http.Error(w, `{"error": "Failure to change username"}`, http.StatusUnauthorized)
}
}
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)
username, ok := s.LoggedIn[clientIP]
timeZone := "UTC"
if ok {
timeZone = s.Database.UserGetTimezone(username)
}
timeLocal := tu.TimeStringToTimeInLocation(msg.Timestamp, timeZone)
edited := ""
if msg.Edited {
edited = "(edited)"
}
body += fmt.Sprintf(`
%s
%s
%s %s
%s
`,
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 {
Username string `json:"username"`
Message string `json:"message"`
}
if err := json.NewDecoder(r.Body).Decode(&msg); err != nil {
http.Error(w, `{"error": "Invalid JSON"}`, http.StatusBadRequest)
return
}
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)
}
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
// }
clientIP := getClientIP(r)
username, ok := s.LoggedIn[clientIP]
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"hasUsername": ok,
"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()
defer s.mu.Unlock()
var users []string
for ip := range s.Connected {
// for all connected, get their usernames
if username, ok := s.LoggedIn[ip]; ok {
users = append(users, username)
}
}
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()
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 {
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),
})
}
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",
})
}
}