add login on front.

This commit is contained in:
emil28092005
2026-01-30 11:54:52 +03:00
parent 58cadadf7f
commit 99a1180bef
4 changed files with 118 additions and 64 deletions
+1
View File
@@ -5,6 +5,7 @@ services:
context: ../.. context: ../..
container_name: shagram-app container_name: shagram-app
environment: environment:
- WS_ALLOWED_ORIGINS=https://localhost,http://localhost
- DATABASE_PATH=/app/data/shagram.db - DATABASE_PATH=/app/data/shagram.db
- JWT_SECRET=${JWT_SECRET} - JWT_SECRET=${JWT_SECRET}
volumes: volumes:
+33 -11
View File
@@ -3,15 +3,30 @@ package api
import ( import (
"log" "log"
"net/http" "net/http"
"os"
"shagram/internal/auth"
"shagram/internal/db" "shagram/internal/db"
"shagram/internal/websocket" "shagram/internal/websocket"
"strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
gws "github.com/gorilla/websocket" gws "github.com/gorilla/websocket"
) )
var upgrader = gws.Upgrader{ var upgrader = gws.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true }, CheckOrigin: func(r *http.Request) bool {
origin := r.Header.Get("Origin")
if origin == "" {
return false
}
allowed := strings.Split(os.Getenv("WS_ALLOWED_ORIGINS"), ",")
for _, a := range allowed {
if strings.TrimSpace(a) == origin {
return true
}
}
return false
},
} }
func WebSocketHandler(hub *websocket.Hub, database *db.DB) gin.HandlerFunc { func WebSocketHandler(hub *websocket.Hub, database *db.DB) gin.HandlerFunc {
@@ -19,6 +34,14 @@ func WebSocketHandler(hub *websocket.Hub, database *db.DB) gin.HandlerFunc {
roomID := c.Param("room") roomID := c.Param("room")
room := hub.GetOrCreateRoom(roomID) room := hub.GetOrCreateRoom(roomID)
tokenString := c.Query("token")
claims, err := auth.ParseAccessToken(tokenString)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
return
}
username := claims.Username
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil { if err != nil {
return return
@@ -35,25 +58,24 @@ func WebSocketHandler(hub *websocket.Hub, database *db.DB) gin.HandlerFunc {
for { for {
var msg map[string]string var msg map[string]string
err := client.Conn.ReadJSON(&msg) if err := client.Conn.ReadJSON(&msg); err != nil {
if err != nil {
break break
} }
user := msg["user"]
if user == "" { text := msg["text"]
user = "Anonymous" if text == "" {
continue
} }
_, err = database.Exec(` _, err = database.Exec(`
INSERT INTO messages (room_id, user, text) INSERT INTO messages (room_id, user, text)
VALUES (?, ?, ?)`, roomID, user, msg["text"]) VALUES (?, ?, ?)`, roomID, username, text)
if err != nil { if err != nil {
log.Printf("Save message error: %v", err) log.Printf("Save message error: %v", err)
} }
formattedMsg := user + ": " + msg["text"]
message := []byte(formattedMsg) room.Broadcast([]byte(username + ": " + text))
room.Broadcast(message)
} }
}() }()
} }
} }
+17 -13
View File
@@ -4,18 +4,18 @@
<title>Shagram Chat</title> <title>Shagram Chat</title>
<style> <style>
body { font-family: Arial; margin: 20px; } body { font-family: Arial; margin: 20px; }
#messages { #messages {
height: 300px; height: 300px;
border: 1px solid #ccc; border: 1px solid #ccc;
overflow-y: auto; overflow-y: auto;
padding: 10px; padding: 10px;
margin-bottom: 10px; margin-bottom: 10px;
background: #f9f9f9; background: #f9f9f9;
} }
.message { .message {
margin: 5px 0; margin: 5px 0;
padding: 8px; padding: 8px;
background: #e3f2fd; background: #e3f2fd;
border-radius: 5px; border-radius: 5px;
border-left: 3px solid #2196F3; border-left: 3px solid #2196F3;
} }
@@ -23,25 +23,29 @@
button { padding: 8px 15px; cursor: pointer; } button { padding: 8px 15px; cursor: pointer; }
select { padding: 8px; width: 320px; } select { padding: 8px; width: 320px; }
#roomsList { margin: 10px 0; } #roomsList { margin: 10px 0; }
#authStatus { margin-left: 10px; }
</style> </style>
</head> </head>
<body> <body>
<h1>📱 Shagram Chat</h1> <h1>📱 Shagram Chat</h1>
<div id="roomsList"> <div id="roomsList">
<select id="roomSelect"> <select id="roomSelect">
<option value="general">general</option> <option value="general">general</option>
</select> </select>
<input type="text" id="userInput" placeholder="Your name" value="Эмиль" maxlength="20"> <input type="text" id="userInput" placeholder="Your name" value="Эмиль" maxlength="20">
<button onclick="login()">Login</button>
<span id="authStatus"></span>
<button onclick="loadRooms()">🔄</button> <button onclick="loadRooms()">🔄</button>
<button onclick="connectRoom()">Connect</button> <button onclick="connectRoom()">Connect</button>
</div> </div>
<div id="messages"></div> <div id="messages"></div>
<div> <div>
<input type="text" id="messageInput" placeholder="Type message..." <input type="text" id="messageInput" placeholder="Type message..."
onkeypress="if(event.key==='Enter') sendMessage()"> onkeypress="if(event.key==='Enter') sendMessage()">
<button onclick="sendMessage()">Send</button> <button onclick="sendMessage()">Send</button>
</div> </div>
+67 -40
View File
@@ -1,71 +1,103 @@
let ws = null; let ws = null;
let accessToken = null;
const messagesDiv = document.getElementById('messages'); const messagesDiv = document.getElementById('messages');
const messageInput = document.getElementById('messageInput'); const messageInput = document.getElementById('messageInput');
const roomSelect = document.getElementById('roomSelect'); const roomSelect = document.getElementById('roomSelect');
const authStatus = document.getElementById('authStatus');
async function login() {
const usernameInput = document.getElementById('userInput');
const username = usernameInput ? usernameInput.value.trim() : '';
if (!username) {
alert('Enter username');
return;
}
authStatus.textContent = 'Logging in...';
const resp = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username }),
});
if (!resp.ok) {
const text = await resp.text();
authStatus.textContent = 'Login failed';
console.error('Login failed:', resp.status, text);
return;
}
const data = await resp.json();
accessToken = data.access_token || null;
if (!accessToken) {
authStatus.textContent = 'Login failed';
return;
}
authStatus.textContent = '✅ Logged in';
}
function loadRooms() { function loadRooms() {
fetch('/api/rooms') fetch('/api/rooms')
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
roomSelect.innerHTML = ''; roomSelect.innerHTML = '';
data.rooms.forEach(room => { (data.rooms || []).forEach(room => {
const option = document.createElement('option'); const option = document.createElement('option');
option.value = room; option.value = room;
option.textContent = room; option.textContent = room;
roomSelect.appendChild(option); roomSelect.appendChild(option);
});
})
.catch(error => {
console.error('Error loading rooms:', error);
}); });
})
.catch(error => {
console.error('Error loading rooms:', error);
})
} }
function connectRoom() { function connectRoom() {
const room = roomSelect.value || 'general'; const room = roomSelect.value || 'general';
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const host = window.location.host; const host = window.location.host;
if (!accessToken) {
alert('Please login first');
return;
}
if (ws) { if (ws) {
ws.close(); ws.close();
} }
const wsUrl = `${protocol}//${host}/ws/${room}`;
const wsUrl = `${protocol}//${host}/ws/${room}?token=${encodeURIComponent(accessToken)}`;
ws = new WebSocket(wsUrl); ws = new WebSocket(wsUrl);
ws.onopen = function() { ws.onopen = function() {
console.log('WebSocket connected, loading history for', room);
fetch(`/api/messages/${room}`) fetch(`/api/messages/${room}`)
.then(response => { .then(response => response.json())
console.log('API response:', response.status);
return response.json()
})
.then(data => { .then(data => {
console.log('History data:', data);
messagesDiv.innerHTML = ''; messagesDiv.innerHTML = '';
const history = data.messages || []; const history = data.messages || [];
console.log('history length:', history.length);
if (history.length > 0) { if (history.length > 0) {
history.reverse().forEach((msg, index) => { history.reverse().forEach(msg => {
console.log(`Message ${index}:`, msg)
const msgDiv = document.createElement('div'); const msgDiv = document.createElement('div');
msgDiv.className = 'message'; msgDiv.className = 'message';
msgDiv.textContent = `${msg.user}: ${msg.text}`; msgDiv.textContent = `${msg.user}: ${msg.text}`;
messagesDiv.appendChild(msgDiv); messagesDiv.appendChild(msgDiv);
}) });
} }
const connectMsg = document.createElement('div'); const connectMsg = document.createElement('div');
connectMsg.className = 'message'; connectMsg.className = 'message';
connectMsg.textContent = `✅ Connected to ${room}`; connectMsg.textContent = `✅ Connected to ${room}`;
messagesDiv.appendChild(connectMsg); messagesDiv.appendChild(connectMsg);
messagesDiv.scrollTop = messagesDiv.scrollHeight; messagesDiv.scrollTop = messagesDiv.scrollHeight;
console.log('History loaded!');
}) })
.catch(error => { .catch(error => {
console.error('❌ History error', error); console.error('History error', error);
}) });
}; };
ws.onmessage = function(event) { ws.onmessage = function(event) {
@@ -88,19 +120,14 @@ function connectRoom() {
function sendMessage() { function sendMessage() {
const text = messageInput.value.trim(); const text = messageInput.value.trim();
const usernameInput = document.getElementById('userInput');
const username = usernameInput ? usernameInput.value.trim() || 'Anonymous' : 'Anonymous';
if (!text || !ws || ws.readyState !== WebSocket.OPEN) { if (!text || !ws || ws.readyState !== WebSocket.OPEN) {
alert('Not connected or empty message'); alert('Not connected or empty message');
return; return;
} }
ws.send(JSON.stringify({ ws.send(JSON.stringify({ text }));
text: text,
user: username,
}));
messageInput.value = ''; messageInput.value = '';
} }
loadRooms(); loadRooms();