--- cmd/server/main.go ---
package main

import (
	"log"
	"net/http"
	"shagram/internal/api"
	"shagram/internal/db"
	"shagram/internal/models"
	"shagram/internal/websocket"

	"github.com/gin-gonic/gin"
)

func main() {
	database, err := db.NewDB("shagram.db")
	if err != nil {
		log.Fatal(err)
	}
	defer database.Close()
	hub := websocket.NewHub()
	hub.GetOrCreateRoom("general")
	hub.GetOrCreateRoom("chat")
	hub.GetOrCreateRoom("dev")

	router := gin.Default()

	router.GET("/ws/:room", func(c *gin.Context) {
		api.WebSocketHandler(hub, database)(c)
	})
	router.GET("/api/rooms", func(c *gin.Context) {
		rows, err := database.Query(`
			SELECT DISTINCT room_id
			FROM messages
			ORDER BY room_id`)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		defer rows.Close()

		var rooms []string
		for rows.Next() {
			var roomID string
			err := rows.Scan(&roomID)
			if err != nil {
				continue
			}
			rooms = append(rooms, roomID)
		}

		if len(rooms) == 0 {
			rooms = []string{"general", "chat", "dev"}
		}

		c.JSON(200, gin.H{"rooms": rooms})
	})
	router.GET("/api/messages/:room", func(c *gin.Context) {
		roomID := c.Param("room")
		rows, err := database.Query(`
			SELECT id, room_id, user, text, created_at
			FROM messages
			WHERE room_id = ?
			ORDER BY created_at DESC
			LIMIT 50`, roomID)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		defer rows.Close()
		var messages []models.Message
		for rows.Next() {
			var msg models.Message
			err := rows.Scan(&msg.ID, &msg.RoomID, &msg.User, &msg.Text, &msg.CreatedAt)
			if err != nil {
				log.Printf("Scan error: %v", err)
				continue
			}
			messages = append(messages, msg)
		}

		c.JSON(200, gin.H{"messages": messages})
	})
	router.Static("/static", "./static")
	router.GET("/", func(c *gin.Context) {
		c.File("./static/index.html")
	})
	router.Run(":8080")
}

--- go.mod ---
module shagram

go 1.25.5

require (
	github.com/bytedance/sonic v1.14.0 // indirect
	github.com/bytedance/sonic/loader v0.3.0 // indirect
	github.com/cloudwego/base64x v0.1.6 // indirect
	github.com/gabriel-vasile/mimetype v1.4.8 // indirect
	github.com/gin-contrib/sse v1.1.0 // indirect
	github.com/gin-gonic/gin v1.11.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.27.0 // indirect
	github.com/goccy/go-json v0.10.2 // indirect
	github.com/goccy/go-yaml v1.18.0 // indirect
	github.com/gorilla/websocket v1.5.3 // indirect
	github.com/json-iterator/go v1.1.12 // indirect
	github.com/klauspost/cpuid/v2 v2.3.0 // indirect
	github.com/leodido/go-urn v1.4.0 // indirect
	github.com/mattn/go-isatty v0.0.20 // indirect
	github.com/mattn/go-sqlite3 v1.14.32 // indirect
	github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
	github.com/modern-go/reflect2 v1.0.2 // indirect
	github.com/pelletier/go-toml/v2 v2.2.4 // indirect
	github.com/quic-go/qpack v0.5.1 // indirect
	github.com/quic-go/quic-go v0.54.0 // indirect
	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
	github.com/ugorji/go/codec v1.3.0 // indirect
	go.uber.org/mock v0.5.0 // indirect
	golang.org/x/arch v0.20.0 // indirect
	golang.org/x/crypto v0.40.0 // indirect
	golang.org/x/mod v0.25.0 // indirect
	golang.org/x/net v0.42.0 // indirect
	golang.org/x/sync v0.16.0 // indirect
	golang.org/x/sys v0.35.0 // indirect
	golang.org/x/text v0.27.0 // indirect
	golang.org/x/tools v0.34.0 // indirect
	google.golang.org/protobuf v1.36.9 // indirect
)

--- go.sum ---
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

--- internal/api/websocket.go ---
package api

import (
	"log"
	"net/http"
	"shagram/internal/db"
	"shagram/internal/websocket"

	"github.com/gin-gonic/gin"
	gws "github.com/gorilla/websocket"
)

var upgrader = gws.Upgrader{
	CheckOrigin: func(r *http.Request) bool { return true },
}

func WebSocketHandler(hub *websocket.Hub, database *db.DB) gin.HandlerFunc {
	return func(c *gin.Context) {
		roomID := c.Param("room")
		room := hub.GetOrCreateRoom(roomID)

		conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
		if err != nil {
			return
		}

		client := &websocket.Client{Conn: conn, Room: room}
		room.Register(client)

		go func() {
			defer func() {
				room.Unregister(client)
				hub.CleanupRoom(roomID)
			}()

			for {
				var msg map[string]string
				err := client.Conn.ReadJSON(&msg)
				if err != nil {
					break
				}
				user := msg["user"]
				if user == "" {
					user = "Anonymous"
				}
				_, err = database.Exec(`
					INSERT INTO messages (room_id, user, text)
					VALUES (?, ?, ?)`, roomID, user, msg["text"])
				if err != nil {
					log.Printf("Save message error: %v", err)
				}

				message := []byte(msg["text"])
				room.Broadcast(message)
			}
		}()
	}

}

--- internal/db/sqlite.go ---
package db

import (
	"database/sql"
	"os"

	_ "github.com/mattn/go-sqlite3"
)

type DB struct {
	*sql.DB
}

func NewDB(path string) (*DB, error) {
	db, err := sql.Open("sqlite3", path)
	if err != nil {
		return nil, err
	}

	schema, err := os.ReadFile("migrations/schema.sql")
	if err != nil {
		return nil, err
	}

	_, err = db.Exec(string(schema))
	if err != nil {
		return nil, err
	}

	return &DB{db}, nil
}

--- internal/models/message.go ---
package models

import "time"

type Message struct {
	ID        int       `json:"id"`
	RoomID    string    `json:"room_id"`
	User      string    `json:"user"`
	Text      string    `json:"text"`
	CreatedAt time.Time `json:"created_at"`
}

type Room struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

--- internal/websocket/hub.go ---
package websocket

type Hub struct {
	rooms map[string]*Room
}

func NewHub() *Hub {
	return &Hub{
		rooms: make(map[string]*Room),
	}
}

func (h *Hub) GetOrCreateRoom(roomID string) *Room {
	if _, ok := h.rooms[roomID]; !ok {
		h.rooms[roomID] = NewRoom(roomID)
	}
	return h.rooms[roomID]
}

func (h *Hub) CleanupRoom(roomID string) {
	room := h.rooms[roomID]
	if len(room.clients) == 0 {
		delete(h.rooms, roomID)
	}
}

func (h *Hub) Rooms() []string {
	ids := make([]string, 0, len(h.rooms))
	for id := range h.rooms {
		ids = append(ids, id)
	}
	return ids
}

--- internal/websocket/room.go ---
package websocket

import "github.com/gorilla/websocket"

type Client struct {
	Conn *websocket.Conn
	Room *Room
}

type Room struct {
	id      string
	clients map[*Client]bool
}

func NewRoom(id string) *Room {
	return &Room{
		id:      id,
		clients: make(map[*Client]bool),
	}
}

func (r *Room) Register(client *Client) {
	r.clients[client] = true
}

func (r *Room) Unregister(client *Client) {
	delete(r.clients, client)
	client.Conn.Close()
}

func (r *Room) Broadcast(message []byte) {
	for client := range r.clients {
		err := client.Conn.WriteMessage(websocket.TextMessage, message)
		if err != nil {
			r.Unregister(client)
		}
	}
}

--- migrations/schema.sql ---
CREATE TABLE IF NOT EXISTS rooms (
    id TEXT PRIMARY KEY,
    name TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS messages (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    room_id TEXT NOT NULL,
    user TEXT NOT NULL,
    text TEXT NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (room_id) REFERENCES rooms (id)
);

INSERT OR IGNORE INTO rooms (id, name) VALUES ('general', 'General');
--- shagram.db ---
SQLite format 3   @                                                                     .
  n]                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       P++Ytablesqlite_sequencesqlite_sequenceCREATE TABLE sqlite_sequence(name,seq)otablemessagesmessagesCREATE TABLE messages (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    room_id TEXT NOT NULL,
    user TEXT NOT NULL,
    text TEXT NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (room_id) REFERENCES rooms (id)
)]tableroomsroomsCREATE TABLE rooms (
    id TEXT PRIMARY KEY,
    name TEXT NOT NULL
))= indexsqlite_autoindex_rooms_1rooms       
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       generalGeneral
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
	general
   2 [5yS-



Z
/
_2                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          + !3chatЭмильку2026-01-13 23:23:54$ 3chatuserAAA2026-01-12 21:59:44( 3chatuserdfgsadf2026-01-12 21:59:35' 3devusertest DB2026-01-12 21:58:42, #3chatusersefsefsefes2026-01-12 21:43:38' 3chatusersefesf2026-01-12 21:43:35) 3chatuserdfbzgdfg2026-01-12 21:43:26( 3chatuserkuykfgh2026-01-12 21:42:52- 3generaluserawfawdfwa2026-01-12 21:28:44' 3devuserwadawdw2026-01-12 21:28:40& 3chatuserefawe2026-01-12 21:28:16'
 3chatuserawdwad2026-01-12 21:27:58$ 3chatuserawd2026-01-12 21:27:25$ 3chatuseradw2026-01-12 21:27:23"
 3chatuserd2026-01-12 21:27:20#	 3chatuserda2026-01-12 21:27:16# 3chatuserku2026-01-12 21:26:58# 3chatuserhi2026-01-12 21:26:43' 3generaluserASD2026-01-12 21:16:37$ 3chatuserASD2026-01-12 21:16:30" 3devuserHI2026-01-12 21:16:08! 3devuser12026-01-12 20:59:23- '3devuserHi from DB! 22026-01-12 20:58:43- '3devuserHi from DB! 12026-01-12 20:58:34
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            messages
--- static/index.html ---
<!DOCTYPE html>
<html>
<head>
    <title>Shagram Chat</title>
    <style>
        body { font-family: Arial; margin: 20px; }
        #messages { 
            height: 300px; 
            border: 1px solid #ccc; 
            overflow-y: auto; 
            padding: 10px; 
            margin-bottom: 10px;
            background: #f9f9f9;
        }
        .message { 
            margin: 5px 0; 
            padding: 8px; 
            background: #e3f2fd; 
            border-radius: 5px;
            border-left: 3px solid #2196F3;
        }
        input { padding: 8px; width: 300px; }
        button { padding: 8px 15px; cursor: pointer; }
        select { padding: 8px; width: 320px; }
        #roomsList { margin: 10px 0; }
    </style>
</head>
<body>
    <h1>📱 Shagram Chat</h1>
    
    <div id="roomsList">
        <select id="roomSelect">
            <option value="general">general</option>
        </select>
        <input type="text" id="userInput" placeholder="Your name" value="Эмиль" maxlength="20">
        <button onclick="loadRooms()">🔄</button>
        <button onclick="connectRoom()">Connect</button>
    </div>

    
    <div id="messages"></div>
    
    <div>
        <input type="text" id="messageInput" placeholder="Type message..." 
               onkeypress="if(event.key==='Enter') sendMessage()">
        <button onclick="sendMessage()">Send</button>
    </div>

    <script src="/static/js/chat.js"></script>
</body>
</html>

--- static/js/chat.js ---
let ws = null;
const messagesDiv = document.getElementById('messages');
const messageInput = document.getElementById('messageInput');
const roomSelect = document.getElementById('roomSelect');

function loadRooms() {
    fetch('/api/rooms')
    .then(response => response.json())
    .then(data => {
        roomSelect.innerHTML = '';
        data.rooms.forEach(room => {
            const option = document.createElement('option');
            option.value = room;
            option.textContent = room;
            roomSelect.appendChild(option);
        });
    })
    .catch(error => {
        console.error('Error loading rooms:', error);
    })
}

function connectRoom() {
    const room = roomSelect.value || 'general';
    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    
    if (ws) {
        ws.close();
    }

    ws = new WebSocket(`${protocol}//localhost:8080/ws/${room}`);

    

    ws.onopen = function() {
        console.log('WebSocket connected, loading history for', room);

        fetch(`/api/messages/${room}`)
            .then(response => {
                console.log('API response:', response.status);
                return response.json()
            })
            .then(data => {
                console.log('History data:', data);
                messagesDiv.innerHTML = '';
                const history = data.messages || [];
                console.log('history length:', history.length);
                if (history.length > 0) {
                    history.reverse().forEach((msg, index) => {
                        console.log(`Message ${index}:`, msg)
                        const msgDiv = document.createElement('div');
                        msgDiv.className = 'message';
                        msgDiv.textContent = `${msg.user}: ${msg.text}`;
                        messagesDiv.appendChild(msgDiv);
                    })
                }
                const connectMsg = document.createElement('div');
                connectMsg.className = 'message';
                connectMsg.textContent = `✅ Connected to ${room}`;
                messagesDiv.appendChild(connectMsg);
                messagesDiv.scrollTop = messagesDiv.scrollHeight;
                console.log('History loaded!');
            })
            .catch(error => {
                console.error('❌ History error', error);
            })

        
    };

    ws.onmessage = function(event) {
        const msgDiv = document.createElement('div');
        msgDiv.className = 'message';
        msgDiv.textContent = event.data;
        messagesDiv.appendChild(msgDiv);
        messagesDiv.scrollTop = messagesDiv.scrollHeight;
    };

    ws.onerror = function(error) {
        console.error('WebSocket error:', error);
        messagesDiv.innerHTML += '<div class="message">❌ Error: ' + error + '</div>';
    };

    ws.onclose = function() {
        messagesDiv.innerHTML += '<div class="message">⛔ Disconnected</div>';
    };
}

function sendMessage() {
    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) {
        alert('Not connected or empty message');
        return;
    }
    
    ws.send(JSON.stringify({
        text: text,
        user: username,
        }));
    messageInput.value = '';
}

loadRooms();
