diff --git a/backend/Makefile b/backend/Makefile index e69de29..d81f66a 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -0,0 +1,18 @@ +# Makefile for backend services + +.PHONY: build up down logs + +build: + docker-compose build + +up: + docker-compose up -d + +down: + docker-compose down + +logs: + docker-compose logs -f + +status: + docker-compose ps diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index e69de29..7b48a21 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -0,0 +1,19 @@ +version: '3.8' + +services: + nginx: + image: nginx:1.25-alpine + ports: + - "80:80" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro + depends_on: + - gateway + + gateway: + build: + context: ./gateway + ports: + - "8080:8080" + environment: + - GO_ENV=production diff --git a/backend/gateway/Dockerfile b/backend/gateway/Dockerfile new file mode 100644 index 0000000..d22f668 --- /dev/null +++ b/backend/gateway/Dockerfile @@ -0,0 +1,19 @@ +FROM golang:1.26.1-alpine3.23 AS builder + +WORKDIR /app + +COPY go.mod ./ +COPY main.go ./ + +RUN go mod download +RUN go build -o gateway main.go + +FROM alpine:latest + +WORKDIR /app + +COPY --from=builder /app/gateway . + +EXPOSE 8080 + +CMD ["./gateway"] \ No newline at end of file diff --git a/backend/gateway/go.mod b/backend/gateway/go.mod new file mode 100644 index 0000000..5d6dccc --- /dev/null +++ b/backend/gateway/go.mod @@ -0,0 +1,35 @@ +module gateway + +go 1.26 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/gorilla/websocket v1.5.1 +) + +require ( + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + yaml.v3 v3.0.1 // indirect +) \ No newline at end of file diff --git a/backend/gateway/main.go b/backend/gateway/main.go new file mode 100644 index 0000000..3c7b3c1 --- /dev/null +++ b/backend/gateway/main.go @@ -0,0 +1,155 @@ +package main + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" +) + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +type Hub struct { + clients map[*Client]bool + broadcast chan []byte + register chan *Client + unregister chan *Client +} + +type Client struct { + hub *Hub + conn *websocket.Conn + send chan []byte +} + +type Message struct { + Type string `json:"type"` + Content string `json:"content"` + Sender string `json:"sender"` +} + +func newHub() *Hub { + return &Hub{ + broadcast: make(chan []byte), + register: make(chan *Client), + unregister: make(chan *Client), + clients: make(map[*Client]bool), + } +} + +func (h *Hub) run() { + for { + select { + case client := <-h.register: + h.clients[client] = true + case client := <-h.unregister: + if _, ok := h.clients[client]; ok { + delete(h.clients, client) + close(client.send) + } + case message := <-h.broadcast: + for client := range h.clients { + select { + case client.send <- message: + default: + close(client.send) + delete(h.clients, client) + } + } + } + } +} + +func (c *Client) readPump() { + defer func() { + c.hub.unregister <- c + c.conn.Close() + }() + + for { + _, message, err := c.conn.ReadMessage() + if err != nil { + if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { + log.Printf("error: %v", err) + } + break + } + c.hub.broadcast <- message + } +} + +func (c *Client) writePump() { + defer func() { + c.conn.Close() + }() + + for { + message, ok := <-c.send + if !ok { + c.conn.WriteMessage(websocket.CloseMessage, []byte{}) + return + } + + w, err := c.conn.NextWriter(websocket.TextMessage) + if err != nil { + return + } + w.Write(message) + + n := len(c.send) + for i := 0; i < n; i++ { + w.Write([]byte("\n")) + w.Write(<-c.send) + } + + if err := w.Close(); err != nil { + return + } + } +} + +func handleWebSocket(hub *Hub) gin.HandlerFunc { + return func(c *gin.Context) { + conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + log.Println(err) + return + } + + client := &Client{ + hub: hub, + conn: conn, + send: make(chan []byte, 256), + } + client.hub.register <- client + + go client.writePump() + go client.readPump() + } +} + +func main() { + hub := newHub() + go hub.run() + + r := gin.Default() + + r.GET("/ws", handleWebSocket(hub)) + r.GET("/health", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "status": "ok", + }) + }) + + log.Println("Gateway server starting on port 8080...") + if err := r.Run(":8080"); err != nil { + log.Fatalf("Failed to start server: %v", err) + } +} \ No newline at end of file