ParleyChat/main.go

385 lines
9.1 KiB
Go
Raw Permalink Normal View History

2026-06-30 11:01:24 +03:00
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})
}