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), }) }