Merge pull request #2 from emil28092005/dev

SHAGRAM 1.0
This commit is contained in:
Emil Shanaty
2026-01-13 01:13:39 +03:00
committed by GitHub
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 (
"log"
"net/http"
"shagram/internal/api"
"shagram/internal/db"
"shagram/internal/models"
"shagram/internal/websocket"
"github.com/gin-gonic/gin"
@@ -16,11 +18,67 @@ func main() {
}
defer database.Close()
hub := websocket.NewHub()
hub.GetOrCreateRoom("general")
hub.GetOrCreateRoom("chat")
hub.GetOrCreateRoom("dev")
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) {
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.GET("/", func(c *gin.Context) {
+10 -1
View File
@@ -1,7 +1,9 @@
package api
import (
"log"
"net/http"
"shagram/internal/db"
"shagram/internal/websocket"
"github.com/gin-gonic/gin"
@@ -12,7 +14,7 @@ var upgrader = gws.Upgrader{
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) {
roomID := c.Param("room")
room := hub.GetOrCreateRoom(roomID)
@@ -37,6 +39,13 @@ func WebSocketHandler(hub *websocket.Hub) gin.HandlerFunc {
if err != nil {
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"])
room.Broadcast(message)
}
+8
View File
@@ -23,3 +23,11 @@ func (h *Hub) CleanupRoom(roomID string) {
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; }
button { padding: 8px 15px; cursor: pointer; }
select { padding: 8px; width: 320px; } /* ← для комнат */
#roomsList { margin: 10px 0; }
</style>
</head>
<body>
<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>
</div>
+63 -6
View File
@@ -1,16 +1,71 @@
let ws = null;
const messagesDiv = document.getElementById('messages');
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() {
const room = roomInput.value || 'general';
const room = roomSelect.value || 'general';
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
if (ws) {
ws.close();
}
ws = new WebSocket(`${protocol}//localhost:8080/ws/${room}`);
ws.onopen = function() {
console.log('Connected to room: ' + room);
messagesDiv.innerHTML = '<div class="message">✅ Connected to ' + room + '</div>';
console.log('WebSocket connected, loading history for', room);
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) {
@@ -27,7 +82,7 @@ function connectRoom() {
};
ws.onclose = function() {
messagesDiv.innerHTML += '<div class="meassage">⛔ Disconnected</div>';
messagesDiv.innerHTML += '<div class="message">⛔ Disconnected</div>';
};
}
@@ -40,4 +95,6 @@ function sendMessage() {
ws.send(JSON.stringify({text: text}));
messageInput.value = '';
}
}
loadRooms();