learning_go/go-code-samples/go-gin-react/go-gin-react-part3/main.go
2025-12-20 10:21:45 +03:00

277 lines
6.9 KiB
Go

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)
}