Загрузить файлы в «/»
This commit is contained in:
parent
3a36e69921
commit
ba744770f1
2 changed files with 405 additions and 0 deletions
21
go.mod
Normal file
21
go.mod
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
module Parley
|
||||||
|
|
||||||
|
go 1.25.0
|
||||||
|
|
||||||
|
require modernc.org/sqlite v1.53.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
golang.org/x/sys v0.44.0 // indirect
|
||||||
|
modernc.org/gc/v3 v3.1.3 // indirect
|
||||||
|
modernc.org/libc v1.73.4 // indirect
|
||||||
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
|
modernc.org/memory v1.11.0 // indirect
|
||||||
|
modernc.org/strutil v1.2.1 // indirect
|
||||||
|
modernc.org/token v1.1.0 // indirect
|
||||||
|
)
|
||||||
384
main.go
Normal file
384
main.go
Normal file
|
|
@ -0,0 +1,384 @@
|
||||||
|
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})
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue