package srv
import (
tu "chat/tu"
"encoding/json"
"fmt"
"net/http"
)
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
}
s.mu.Lock()
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)
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)
s.mu.Unlock()
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"})
}
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)
timeZone := s.Database.UserGetTimezone(clientIP)
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")
// 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,
})
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
}
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")
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),
})
}