384 lines
9.1 KiB
Go
384 lines
9.1 KiB
Go
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})
|
||
}
|