ParleyChat/main.go

384 lines
9.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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