-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.go
276 lines (230 loc) · 6.89 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
package main
import (
"database/sql"
"fmt"
"log"
"net/http"
"os"
"strconv"
"github.com/gin-gonic/gin"
_ "github.com/glebarez/go-sqlite"
)
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Password string `json:"password"`
}
type Channel struct {
ID int `json:"id"`
Name string `json:"name"`
}
type Message struct {
ID int `json:"id"`
ChannelID int `json:"channel_id"`
UserID int `json:"user_id"`
UserName string `json:"user_name"`
Text string `json:"text"`
}
func main() {
// Get the working directory
wd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
// Print the working directory
fmt.Println("Working directory:", wd)
// Open the SQLite database file
db, err := sql.Open("sqlite", wd+"./database.db")
defer func(db *sql.DB) {
err := db.Close()
if err != nil {
log.Fatal(err)
}
}(db)
// Create the Gin router
r := gin.Default()
if err != nil {
log.Fatal(err)
}
// Creation endpoints
r.POST("/users", func(c *gin.Context) { createUser(c, db) })
r.POST("/channels", func(c *gin.Context) { createChannel(c, db) })
r.POST("/messages", func(c *gin.Context) { createMessage(c, db) })
// Listing endpoints
r.GET("/channels", func(c *gin.Context) { listChannels(c, db) })
r.GET("/messages", func(c *gin.Context) { listMessages(c, db) })
// Login endpoint
r.POST("/login", func(c *gin.Context) { login(c, db) })
// Explicitly serve index.html at the root
r.StaticFile("/", "chat-ui/build/index.html")
// Serve static files under /static
r.StaticFS("/static", http.Dir("chat-ui/build/static"))
err = r.Run(":8080")
if err != nil {
log.Fatal(err)
}
}
// User creation endpoint.
func createUser(c *gin.Context, db *sql.DB) {
// Parse JSON request body into User struct
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Insert user into database
result, err := db.Exec("INSERT INTO users (username, password) VALUES (?, ?)", user.Username, user.Password)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Get ID of newly inserted user
id, err := result.LastInsertId()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Return ID of newly inserted user
c.JSON(http.StatusOK, gin.H{"id": id})
}
// Login endpoint.
func login(c *gin.Context, db *sql.DB) {
// Parse JSON request body into User struct
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Query database for user
row := db.QueryRow("SELECT id FROM users WHERE username = ? AND password = ?", user.Username, user.Password)
// Get ID of user
var id int
err := row.Scan(&id)
if err != nil {
// Check if user was not found
if err == sql.ErrNoRows {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid username or password"})
return
}
// Return error if other error occurred
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
// Return ID of user
c.JSON(http.StatusOK, gin.H{"id": id})
}
// Channel creation endpoint.
func createChannel(c *gin.Context, db *sql.DB) {
// Parse JSON request body into Channel struct
var channel Channel
if err := c.ShouldBindJSON(&channel); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Insert channel into database
result, err := db.Exec("INSERT INTO channels (name) VALUES (?)", channel.Name)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Get ID of newly inserted channel
id, err := result.LastInsertId()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Return ID of newly inserted channel
c.JSON(http.StatusOK, gin.H{"id": id})
}
// Channel listing endpoint.
func listChannels(c *gin.Context, db *sql.DB) {
// Query database for channels
rows, err := db.Query("SELECT id, name FROM channels")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Create slice of channels
var channels []Channel
// Iterate over rows
for rows.Next() {
// Create new channel
var channel Channel
// Scan row into channel
err := rows.Scan(&channel.ID, &channel.Name)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Append channel to slice
channels = append(channels, channel)
}
// Return slice of channels
c.JSON(http.StatusOK, channels)
}
// Message creation endpoint.
func createMessage(c *gin.Context, db *sql.DB) {
// Parse JSON request body into Message struct
var message Message
if err := c.ShouldBindJSON(&message); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Insert message into database
result, err := db.Exec("INSERT INTO messages (channel_id, user_id, message) VALUES (?, ?, ?)", message.ChannelID, message.UserID, message.Text)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Get ID of newly inserted message
id, err := result.LastInsertId()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Return ID of newly inserted message
c.JSON(http.StatusOK, gin.H{"id": id})
}
// Message listing endpoint.
func listMessages(c *gin.Context, db *sql.DB) {
// Parse channel ID from URL
channelID, err := strconv.Atoi(c.Query("channelID"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Parse optional limit query parameter from URL
limit, err := strconv.Atoi(c.Query("limit"))
if err != nil {
// Set limit to 100 if not provided
limit = 100
}
// Parse last message ID query parameter from URL. This is used to get messages after a certain message.
lastMessageID, err := strconv.Atoi(c.Query("lastMessageID"))
if err != nil {
// Set last message ID to 0 if not provided
lastMessageID = 0
}
// Query database for messages
rows, err := db.Query("SELECT m.id, channel_id, user_id, u.username AS user_name, message FROM messages m LEFT JOIN users u ON u.id = m.user_id WHERE channel_id = ? AND m.id > ? ORDER BY m.id ASC LIMIT ?", channelID, lastMessageID, limit)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Create slice of messages
var messages []Message
// Iterate over rows
for rows.Next() {
// Create new message
var message Message
// Scan row into message
err := rows.Scan(&message.ID, &message.ChannelID, &message.UserID, &message.UserName, &message.Text)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Append message to slice
messages = append(messages, message)
}
// Return slice of messages
c.JSON(http.StatusOK, messages)
}