Skip to content

Commit ab8015f

Browse files
committed
Add simple frontend
1 parent 9ec2e6e commit ab8015f

File tree

4 files changed

+218
-3
lines changed

4 files changed

+218
-3
lines changed

cmd/server/main.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"strings"
1919
"time"
2020

21+
"github.com/gin-contrib/cors"
2122
"github.com/gin-gonic/gin"
2223
)
2324

@@ -129,6 +130,13 @@ func main() {
129130

130131
r := gin.Default()
131132

133+
r.Use(cors.New(cors.Config{
134+
AllowOrigins: []string{"*"},
135+
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
136+
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
137+
AllowCredentials: true,
138+
}))
139+
132140
r.POST("/register", auth.Register)
133141
r.POST("/login", auth.Login)
134142

frontend/index.html

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Simple Chat Frontend</title>
5+
<style>
6+
#messages {
7+
border: 1px solid black;
8+
height: 300px;
9+
overflow-y: auto;
10+
margin-bottom: 10px;
11+
}
12+
</style>
13+
</head>
14+
<body>
15+
<h2>Register</h2>
16+
<input id="regUsername" placeholder="Username" />
17+
<input id="regPassword" type="password" placeholder="Password" />
18+
<button id="registerBtn">Register</button>
19+
<p id="registerStatus"></p>
20+
21+
<h2>Login</h2>
22+
<input id="loginUsername" placeholder="Username" />
23+
<input id="loginPassword" type="password" placeholder="Password" />
24+
<button id="loginBtn">Login</button>
25+
<p id="loginStatus"></p>
26+
27+
<div id="chat" style="display:none;">
28+
<h2>Chat</h2>
29+
<div id="messages"></div>
30+
<input id="messageInput" placeholder="Type a message" />
31+
<button id="sendBtn">Send</button>
32+
<button id="logoutBtn">Logout</button>
33+
</div>
34+
35+
<script>
36+
let token = null;
37+
let ws = null;
38+
39+
document.getElementById('registerBtn').onclick = async () => {
40+
const username = document.getElementById('regUsername').value.trim();
41+
const password = document.getElementById('regPassword').value.trim();
42+
43+
if (!username || !password) {
44+
document.getElementById('registerStatus').innerText = 'Fill in username and password';
45+
return;
46+
}
47+
48+
const res = await fetch('http://localhost:8080/register', {
49+
method: 'POST',
50+
headers: {'Content-Type': 'application/json'},
51+
body: JSON.stringify({username, password})
52+
});
53+
54+
if (res.ok) {
55+
document.getElementById('registerStatus').innerText = 'Registered successfully. Please login.';
56+
} else {
57+
const data = await res.json();
58+
document.getElementById('registerStatus').innerText = 'Registration failed: ' + (data.error || 'Unknown error');
59+
}
60+
};
61+
62+
document.getElementById('loginBtn').onclick = async () => {
63+
const username = document.getElementById('loginUsername').value.trim();
64+
const password = document.getElementById('loginPassword').value.trim();
65+
66+
if (!username || !password) {
67+
document.getElementById('loginStatus').innerText = 'Fill in username and password';
68+
return;
69+
}
70+
71+
const res = await fetch('http://localhost:8080/login', {
72+
method: 'POST',
73+
headers: {'Content-Type': 'application/json'},
74+
body: JSON.stringify({username, password})
75+
});
76+
77+
if (res.ok) {
78+
const data = await res.json();
79+
token = data.token;
80+
document.getElementById('loginStatus').innerText = '';
81+
document.getElementById('chat').style.display = 'block';
82+
await loadMessages();
83+
setupWebSocket();
84+
} else {
85+
document.getElementById('loginStatus').innerText = 'Login failed';
86+
}
87+
};
88+
89+
async function loadMessages() {
90+
const res = await fetch('http://localhost:8080/messages', {
91+
headers: {
92+
'Authorization': 'Bearer ' + token
93+
}
94+
});
95+
if (!res.ok) {
96+
console.error('Failed to load messages');
97+
return;
98+
}
99+
const messages = await res.json();
100+
console.log(messages);
101+
const container = document.getElementById('messages');
102+
container.innerHTML = '';
103+
104+
messages.forEach(msgObj => {
105+
const msgDiv = document.createElement('div');
106+
msgDiv.style.marginBottom = "15px";
107+
108+
const sender = document.createElement('div');
109+
sender.textContent = msgObj.UserID || `User ${msgObj.user_id}`;
110+
sender.style.fontWeight = 'bold';
111+
112+
const content = document.createElement('div');
113+
content.textContent = msgObj.Content;
114+
115+
const timestamp = document.createElement('div');
116+
const dt = new Date(msgObj.CreatedAt || msgObj.timestamp);
117+
timestamp.textContent = dt.toLocaleDateString() + ' ' + dt.toLocaleTimeString();
118+
timestamp.style.fontSize = 'small';
119+
timestamp.style.color = 'gray';
120+
121+
msgDiv.appendChild(sender);
122+
msgDiv.appendChild(content);
123+
msgDiv.appendChild(timestamp);
124+
125+
container.appendChild(msgDiv);
126+
});
127+
128+
container.scrollTop = container.scrollHeight;
129+
}
130+
131+
function setupWebSocket() {
132+
ws = new WebSocket(`ws://localhost:8080/ws?token=${token}`);
133+
134+
ws.onmessage = (event) => {
135+
const msgObj = JSON.parse(event.data);
136+
137+
const msgDiv = document.createElement('div');
138+
msgDiv.style.marginBottom = "15px";
139+
140+
const sender = document.createElement('div');
141+
sender.textContent = msgObj.user;
142+
sender.style.fontWeight = 'bold';
143+
144+
const content = document.createElement('div');
145+
content.textContent = msgObj.content;
146+
147+
const timestamp = document.createElement('div');
148+
const dt = new Date(msgObj.timestamp);
149+
timestamp.textContent = dt.toLocaleDateString();
150+
timestamp.style.fontSize = 'small';
151+
timestamp.style.color = 'gray';
152+
153+
msgDiv.appendChild(sender);
154+
msgDiv.appendChild(content);
155+
msgDiv.appendChild(timestamp);
156+
157+
const messagesContainer = document.getElementById('messages');
158+
messagesContainer.appendChild(msgDiv);
159+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
160+
};
161+
162+
ws.onclose = () => {
163+
alert('WebSocket closed.');
164+
logout();
165+
};
166+
}
167+
168+
document.getElementById('sendBtn').onclick = async () => {
169+
const content = document.getElementById('messageInput').value.trim();
170+
if (!content) return;
171+
172+
await fetch('http://localhost:8080/messages', {
173+
method: 'POST',
174+
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
175+
body: JSON.stringify({content})
176+
});
177+
178+
const msgDiv = document.createElement('div');
179+
msgDiv.textContent = 'You: ' + content;
180+
document.getElementById('messages').appendChild(msgDiv);
181+
document.getElementById('messages').scrollTop = document.getElementById('messages').scrollHeight;
182+
document.getElementById('messageInput').value = '';
183+
};
184+
185+
document.getElementById('logoutBtn').onclick = () => {
186+
logout();
187+
};
188+
189+
function logout() {
190+
token = null;
191+
if (ws) ws.close();
192+
document.getElementById('chat').style.display = 'none';
193+
document.getElementById('loginStatus').innerText = '';
194+
document.getElementById('messages').innerHTML = '';
195+
}
196+
</script>
197+
</body>
198+
</html>

go.mod

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ require (
66
github.com/bytedance/sonic v1.14.0 // indirect
77
github.com/bytedance/sonic/loader v0.3.0 // indirect
88
github.com/cloudwego/base64x v0.1.6 // indirect
9-
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
9+
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
10+
github.com/gin-contrib/cors v1.7.6 // indirect
1011
github.com/gin-contrib/sse v1.1.0 // indirect
1112
github.com/gin-gonic/gin v1.11.0 // indirect
1213
github.com/go-playground/locales v0.14.1 // indirect
1314
github.com/go-playground/universal-translator v0.18.1 // indirect
1415
github.com/go-playground/validator/v10 v10.27.0 // indirect
15-
github.com/goccy/go-json v0.10.2 // indirect
16+
github.com/goccy/go-json v0.10.5 // indirect
1617
github.com/goccy/go-yaml v1.18.0 // indirect
1718
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
1819
github.com/gorilla/websocket v1.5.3 // indirect
@@ -22,7 +23,7 @@ require (
2223
github.com/leodido/go-urn v1.4.0 // indirect
2324
github.com/lib/pq v1.10.9 // indirect
2425
github.com/mattn/go-isatty v0.0.20 // indirect
25-
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
26+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
2627
github.com/modern-go/reflect2 v1.0.2 // indirect
2728
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
2829
github.com/quic-go/qpack v0.5.1 // indirect

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
99
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1010
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
1111
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
12+
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
13+
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
14+
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
15+
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
1216
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
1317
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
1418
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
@@ -22,6 +26,8 @@ github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAu
2226
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
2327
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
2428
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
29+
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
30+
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
2531
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
2632
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
2733
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
@@ -44,6 +50,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
4450
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
4551
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
4652
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
53+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
54+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
4755
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
4856
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
4957
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=

0 commit comments

Comments
 (0)