SHAGRAM 1.0

This commit is contained in:
emil28092005
2026-01-13 01:07:56 +03:00
parent 04646192bc
commit 975aea12c9
8 changed files with 301 additions and 23 deletions
+1
View File
@@ -0,0 +1 @@
project_context.txt
+60 -2
View File
@@ -2,8 +2,10 @@ package main
import ( import (
"log" "log"
"net/http"
"shagram/internal/api" "shagram/internal/api"
"shagram/internal/db" "shagram/internal/db"
"shagram/internal/models"
"shagram/internal/websocket" "shagram/internal/websocket"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -16,11 +18,67 @@ func main() {
} }
defer database.Close() defer database.Close()
hub := websocket.NewHub() hub := websocket.NewHub()
hub.GetOrCreateRoom("general")
hub.GetOrCreateRoom("chat")
hub.GetOrCreateRoom("dev")
router := gin.Default() router := gin.Default()
router.GET("/ws/:room", api.WebSocketHandler(hub)) router.GET("/ws/:room", func(c *gin.Context) {
api.WebSocketHandler(hub, database)(c)
})
router.GET("/api/rooms", func(c *gin.Context) { router.GET("/api/rooms", func(c *gin.Context) {
c.JSON(200, gin.H{"rooms": "TODO"}) rows, err := database.Query(`
SELECT DISTINCT room_id
FROM messages
ORDER BY room_id`)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
defer rows.Close()
var rooms []string
for rows.Next() {
var roomID string
err := rows.Scan(&roomID)
if err != nil {
continue
}
rooms = append(rooms, roomID)
}
if len(rooms) == 0 {
rooms = []string{"general", "chat", "dev"}
}
c.JSON(200, gin.H{"rooms": rooms})
})
router.GET("/api/messages/:room", func(c *gin.Context) {
roomID := c.Param("room")
rows, err := database.Query(`
SELECT id, room_id, user, text, created_at
FROM messages
WHERE room_id = ?
ORDER BY created_at DESC
LIMIT 50`, roomID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
defer rows.Close()
var messages []models.Message
for rows.Next() {
var msg models.Message
err := rows.Scan(&msg.ID, &msg.RoomID, &msg.User, &msg.Text, &msg.CreatedAt)
if err != nil {
log.Printf("Scan error: %v", err)
continue
}
messages = append(messages, msg)
}
c.JSON(200, gin.H{"messages": messages})
}) })
router.Static("/static", "./static") router.Static("/static", "./static")
router.GET("/", func(c *gin.Context) { router.GET("/", func(c *gin.Context) {
+10 -1
View File
@@ -1,7 +1,9 @@
package api package api
import ( import (
"log"
"net/http" "net/http"
"shagram/internal/db"
"shagram/internal/websocket" "shagram/internal/websocket"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -12,7 +14,7 @@ var upgrader = gws.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true }, CheckOrigin: func(r *http.Request) bool { return true },
} }
func WebSocketHandler(hub *websocket.Hub) gin.HandlerFunc { func WebSocketHandler(hub *websocket.Hub, database *db.DB) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
roomID := c.Param("room") roomID := c.Param("room")
room := hub.GetOrCreateRoom(roomID) room := hub.GetOrCreateRoom(roomID)
@@ -37,6 +39,13 @@ func WebSocketHandler(hub *websocket.Hub) gin.HandlerFunc {
if err != nil { if err != nil {
break break
} }
_, err = database.Exec(`
INSERT INTO messages (room_id, user, text)
VALUES (?, ?, ?)`, roomID, "user", msg["text"])
if err != nil {
log.Printf("Save message error: %v", err)
}
message := []byte(msg["text"]) message := []byte(msg["text"])
room.Broadcast(message) room.Broadcast(message)
} }
+8
View File
@@ -23,3 +23,11 @@ func (h *Hub) CleanupRoom(roomID string) {
delete(h.rooms, roomID) delete(h.rooms, roomID)
} }
} }
func (h *Hub) Rooms() []string {
ids := make([]string, 0, len(h.rooms))
for id := range h.rooms {
ids = append(ids, id)
}
return ids
}
+151 -12
View File
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.
+8 -2
View File
@@ -21,13 +21,19 @@
} }
input { padding: 8px; width: 300px; } input { padding: 8px; width: 300px; }
button { padding: 8px 15px; cursor: pointer; } button { padding: 8px 15px; cursor: pointer; }
select { padding: 8px; width: 320px; } /* ← для комнат */
#roomsList { margin: 10px 0; }
</style> </style>
</head> </head>
<body> <body>
<h1>📱 Shagram Chat</h1> <h1>📱 Shagram Chat</h1>
<div> <!-- ← НОВЫЙ БЛОК С КОМНАТАМИ -->
<input type="text" id="roomInput" placeholder="Room name" value="general"> <div id="roomsList">
<select id="roomSelect">
<option value="general">general</option>
</select>
<button onclick="loadRooms()">🔄 Refresh</button>
<button onclick="connectRoom()">Connect</button> <button onclick="connectRoom()">Connect</button>
</div> </div>
+62 -5
View File
@@ -1,16 +1,71 @@
let ws = null; let ws = null;
const messagesDiv = document.getElementById('messages'); const messagesDiv = document.getElementById('messages');
const messageInput = document.getElementById('messageInput'); const messageInput = document.getElementById('messageInput');
const roomInput = document.getElementById('roomInput'); const roomSelect = document.getElementById('roomSelect');
function loadRooms() {
fetch('/api/rooms')
.then(response => response.json())
.then(data => {
roomSelect.innerHTML = '';
data.rooms.forEach(room => {
const option = document.createElement('option');
option.value = room;
option.textContent = room;
roomSelect.appendChild(option);
});
})
.catch(error => {
console.error('Error loading rooms:', error);
})
}
function connectRoom() { function connectRoom() {
const room = roomInput.value || 'general'; const room = roomSelect.value || 'general';
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
if (ws) {
ws.close();
}
ws = new WebSocket(`${protocol}//localhost:8080/ws/${room}`); ws = new WebSocket(`${protocol}//localhost:8080/ws/${room}`);
ws.onopen = function() { ws.onopen = function() {
console.log('Connected to room: ' + room); console.log('WebSocket connected, loading history for', room);
messagesDiv.innerHTML = '<div class="message">✅ Connected to ' + room + '</div>';
fetch(`/api/messages/${room}`)
.then(response => {
console.log('API response:', response.status);
return response.json()
})
.then(data => {
console.log('History data:', data);
messagesDiv.innerHTML = '';
const history = data.messages || [];
console.log('history length:', history.length);
if (history.length > 0) {
history.reverse().forEach((msg, index) => {
console.log(`Message ${index}:`, msg)
const msgDiv = document.createElement('div');
msgDiv.className = 'message';
msgDiv.textContent = `${msg.user}: ${msg.text}`;
messagesDiv.appendChild(msgDiv);
})
}
const connectMsg = document.createElement('div');
connectMsg.className = 'message';
connectMsg.textContent = `✅ Connected to ${room}`;
messagesDiv.appendChild(connectMsg);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
console.log('History loaded!');
})
.catch(error => {
console.error('❌ History error', error);
})
}; };
ws.onmessage = function(event) { ws.onmessage = function(event) {
@@ -27,7 +82,7 @@ function connectRoom() {
}; };
ws.onclose = function() { ws.onclose = function() {
messagesDiv.innerHTML += '<div class="meassage">⛔ Disconnected</div>'; messagesDiv.innerHTML += '<div class="message">⛔ Disconnected</div>';
}; };
} }
@@ -41,3 +96,5 @@ function sendMessage() {
ws.send(JSON.stringify({text: text})); ws.send(JSON.stringify({text: text}));
messageInput.value = ''; messageInput.value = '';
} }
loadRooms();