package main import ( "database/sql" "encoding/json" "fmt" "html/template" "log" "net/http" "os" "strconv" "strings" "time" _ "modernc.org/sqlite" ) var ( db *sql.DB basePath string ) type User struct { ID int `json:"id"` Username string `json:"username"` Password string `json:"password"` Active bool `json:"active"` IsAdmin bool `json:"is_admin"` } type Message struct { ID int `json:"id"` FromID int `json:"from_id"` ToID int `json:"to_id"` Text string `json:"text"` Username string `json:"username"` Timestamp int64 `json:"timestamp"` } func main() { // Получаем базовый путь из переменной окружения basePath = os.Getenv("BASE_PATH") if basePath == "" { basePath = "/" } // Убираем слеш в конце если есть basePath = strings.TrimSuffix(basePath, "/") if basePath == "" { basePath = "/" } var err error db, err = sql.Open("sqlite", "./chat.db") if err != nil { log.Fatal(err) } defer db.Close() initDB() // Роуты с учетом базового пути http.HandleFunc(basePath+"/", serveIndex) http.HandleFunc(basePath+"/api/register", handleRegister) http.HandleFunc(basePath+"/api/login", handleLogin) http.HandleFunc(basePath+"/api/users", handleGetUsers) http.HandleFunc(basePath+"/api/messages", handleMessages) http.HandleFunc(basePath+"/api/admin/users", handleAdminGetUsers) http.HandleFunc(basePath+"/api/admin/toggle", handleAdminToggle) // Статические файлы fs := http.FileServer(http.Dir("static")) http.Handle(basePath+"/static/", http.StripPrefix(basePath+"/static/", fs)) fmt.Printf("Чат запущен на :3100 (базовый путь: %s)\n", basePath) log.Fatal(http.ListenAndServe(":3100", nil)) } func initDB() { queries := []string{ `CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password TEXT NOT NULL, active INTEGER DEFAULT 0, is_admin INTEGER DEFAULT 0 )`, `CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, from_id INTEGER NOT NULL, to_id INTEGER NOT NULL, text TEXT NOT NULL, timestamp INTEGER NOT NULL )`, `INSERT OR IGNORE INTO users (username, password, active, is_admin) -- // ПОМЕНЯТЬ НА СВОИ VALUES ('admin', 'admin123', 1, 1)`, } for _, q := range queries { if _, err := db.Exec(q); err != nil { log.Printf("Error executing query: %v", err) } } } func serveIndex(w http.ResponseWriter, r *http.Request) { // Перенаправляем /parley на /parley/ if r.URL.Path == basePath && basePath != "/" { http.Redirect(w, r, basePath+"/", http.StatusMovedPermanently) return } tmpl, err := template.ParseFiles("static/index.html") if err != nil { http.Error(w, "Template error", 500) return } data := struct { BasePath string }{ BasePath: basePath, } tmpl.Execute(w, data) } func handleRegister(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, "Method not allowed", 405) return } var user User if err := json.NewDecoder(r.Body).Decode(&user); err != nil { jsonError(w, "Invalid request") return } if user.Username == "" || user.Password == "" { jsonError(w, "Username and password required") return } _, err := db.Exec("INSERT INTO users (username, password) VALUES (?, ?)", user.Username, user.Password) if err != nil { if strings.Contains(err.Error(), "UNIQUE") { jsonError(w, "Username already exists") } else { jsonError(w, "Registration failed") } return } jsonResponse(w, map[string]interface{}{"message": "Registration successful. Wait for admin activation."}) } func handleLogin(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, "Method not allowed", 405) return } var user User if err := json.NewDecoder(r.Body).Decode(&user); err != nil { jsonError(w, "Invalid request") return } var dbUser User var activeInt, adminInt int err := db.QueryRow("SELECT id, username, active, is_admin FROM users WHERE username = ? AND password = ?", user.Username, user.Password).Scan(&dbUser.ID, &dbUser.Username, &activeInt, &adminInt) if err == sql.ErrNoRows { jsonError(w, "Invalid credentials") return } if err != nil { jsonError(w, "Database error") return } dbUser.Active = activeInt == 1 dbUser.IsAdmin = adminInt == 1 if !dbUser.Active { jsonError(w, "Account not activated. Contact admin.") return } jsonResponse(w, map[string]interface{}{ "user_id": dbUser.ID, "username": dbUser.Username, "is_admin": dbUser.IsAdmin, }) } func handleGetUsers(w http.ResponseWriter, r *http.Request) { userID, err := strconv.Atoi(r.URL.Query().Get("user_id")) if err != nil { jsonError(w, "Invalid user_id") return } // Проверяем, активен ли пользователь var activeInt int err = db.QueryRow("SELECT active FROM users WHERE id = ?", userID).Scan(&activeInt) if err != nil || activeInt != 1 { jsonError(w, "User not active") return } rows, err := db.Query("SELECT id, username FROM users WHERE active = 1 AND id != ?", userID) if err != nil { jsonError(w, "Database error") return } defer rows.Close() var users []map[string]interface{} for rows.Next() { var id int var username string rows.Scan(&id, &username) users = append(users, map[string]interface{}{ "id": id, "username": username, }) } jsonResponse(w, users) } func handleMessages(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": getMessages(w, r) case "POST": sendMessage(w, r) default: http.Error(w, "Method not allowed", 405) } } func getMessages(w http.ResponseWriter, r *http.Request) { userID, _ := strconv.Atoi(r.URL.Query().Get("user_id")) partnerID, _ := strconv.Atoi(r.URL.Query().Get("partner_id")) rows, err := db.Query(` SELECT m.id, m.from_id, m.to_id, m.text, m.timestamp, u.username FROM messages m JOIN users u ON m.from_id = u.id WHERE (m.from_id = ? AND m.to_id = ?) OR (m.from_id = ? AND m.to_id = ?) ORDER BY m.timestamp ASC LIMIT 100`, userID, partnerID, partnerID, userID) if err != nil { jsonError(w, "Database error") return } defer rows.Close() var messages []Message for rows.Next() { var msg Message rows.Scan(&msg.ID, &msg.FromID, &msg.ToID, &msg.Text, &msg.Timestamp, &msg.Username) messages = append(messages, msg) } if messages == nil { messages = []Message{} } jsonResponse(w, messages) } func sendMessage(w http.ResponseWriter, r *http.Request) { var msg Message if err := json.NewDecoder(r.Body).Decode(&msg); err != nil { jsonError(w, "Invalid request") return } msg.Timestamp = time.Now().Unix() result, err := db.Exec("INSERT INTO messages (from_id, to_id, text, timestamp) VALUES (?, ?, ?, ?)", msg.FromID, msg.ToID, msg.Text, msg.Timestamp) if err != nil { jsonError(w, "Failed to send message") return } id, _ := result.LastInsertId() msg.ID = int(id) // Получаем username отправителя db.QueryRow("SELECT username FROM users WHERE id = ?", msg.FromID).Scan(&msg.Username) jsonResponse(w, msg) } func handleAdminGetUsers(w http.ResponseWriter, r *http.Request) { adminID, err := strconv.Atoi(r.URL.Query().Get("admin_id")) if err != nil { jsonError(w, "Invalid admin_id") return } // Проверяем, что запрос от админа var isAdmin int db.QueryRow("SELECT is_admin FROM users WHERE id = ?", adminID).Scan(&isAdmin) if isAdmin != 1 { jsonError(w, "Not admin") return } rows, err := db.Query("SELECT id, username, active FROM users") if err != nil { jsonError(w, "Database error") return } defer rows.Close() var users []map[string]interface{} for rows.Next() { var id, active int var username string rows.Scan(&id, &username, &active) users = append(users, map[string]interface{}{ "id": id, "username": username, "active": active == 1, }) } jsonResponse(w, users) } func handleAdminToggle(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, "Method not allowed", 405) return } var req struct { AdminID int `json:"admin_id"` UserID int `json:"user_id"` Active bool `json:"active"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { jsonError(w, "Invalid request") return } // Проверяем, что запрос от админа var isAdmin int db.QueryRow("SELECT is_admin FROM users WHERE id = ?", req.AdminID).Scan(&isAdmin) if isAdmin != 1 { jsonError(w, "Not admin") return } activeInt := 0 if req.Active { activeInt = 1 } db.Exec("UPDATE users SET active = ? WHERE id = ?", activeInt, req.UserID) jsonResponse(w, map[string]string{"message": "User status updated"}) } func jsonResponse(w http.ResponseWriter, data interface{}) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(data) } func jsonError(w http.ResponseWriter, message string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(map[string]string{"error": message}) }