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