├── controllers
├── controller.go
├── auth_api.go
├── user_api.go
├── room_api.go
└── api_test.go
├── docker-compose.yaml
├── .gitignore
├── Dockerfile
├── log
├── logger_test.go
└── logger.go
├── models
├── room.go
├── broker.go
├── chat_event.go
└── client.go
├── database
├── utils.go
├── connect_database.go
├── mongo.go
└── mongo_test.go
├── README.MD
├── main.go
├── websocket
├── wsServer.go
└── manager.go
├── go.mod
├── templates
├── chat.tmpl
└── index.tmpl
└── go.sum
/controllers/controller.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 |
5 | "chicko_chat/database"
6 |
7 | )
8 |
9 | type Controller struct {
10 | DB *database.ChatDatabase
11 | }
12 |
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: "3.9"
2 | services:
3 | web:
4 | build: .
5 | ports:
6 | - "8080:8080"
7 | mongo:
8 | container_name: mongo
9 | image: mongo:4.4
10 | ports:
11 | - "27017:27017"
12 | command: mongod
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories (remove the comment below to include it)
15 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.20-buster
2 |
3 |
4 |
5 | WORKDIR /go/src/app
6 | COPY . .
7 |
8 |
9 | # Build the Go app
10 | RUN GOPROXY=https://goproxy.cn go get github.com/klauspost/compress
11 | RUN go build -o chat .
12 |
13 | # Expose port 8080 to the outside world
14 | EXPOSE 8009
15 |
16 | # Command to run the executable
17 | CMD ["./chat"]
--------------------------------------------------------------------------------
/log/logger_test.go:
--------------------------------------------------------------------------------
1 | package logger
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 | )
7 |
8 | func TestNew(t *testing.T) {
9 | var buf bytes.Buffer
10 | logger := New(&buf)
11 | if logger == nil {
12 | t.Error("should not be nil")
13 | } else {
14 | logger.Log("log package.")
15 | if buf.String() != "log package.\n" {
16 | t.Errorf("Trace should not write '%s'.", buf.String())
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/models/room.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "time"
5 |
6 | "go.mongodb.org/mongo-driver/bson/primitive"
7 | )
8 |
9 | // struct representing a chat room
10 | type ChatRoom struct {
11 | ID primitive.ObjectID `json:"_id" bson:"_id,omitempty"`
12 | Title string `json:"title" bson:"title"`
13 | CreatedAt time.Time `json:"createdAt" bson:"created"`
14 | Clients []primitive.ObjectID `json:"users" bson:"users"`
15 | }
16 |
--------------------------------------------------------------------------------
/log/logger.go:
--------------------------------------------------------------------------------
1 | package logger
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | )
7 |
8 | // logging events throughout code.
9 | type Logger interface {
10 | Log(...interface{})
11 | }
12 |
13 | // New creates a new Logger that will write the output to
14 | // the specified io.Writer.
15 | func New(w io.Writer) Logger {
16 | return &logger{out: w}
17 | }
18 |
19 | type logger struct {
20 | out io.Writer
21 | }
22 |
23 | // writes the arguments io.Writer.
24 | func (t *logger) Log(a ...interface{}) {
25 | fmt.Fprint(t.out, a...)
26 | fmt.Fprintln(t.out)
27 | }
28 |
--------------------------------------------------------------------------------
/database/utils.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import (
4 | "errors"
5 | "go.mongodb.org/mongo-driver/bson/primitive"
6 | "go.mongodb.org/mongo-driver/mongo"
7 | )
8 |
9 |
10 | // For converting result of insert document to type primitive.ObjectID
11 | func convertId(result *mongo.InsertOneResult) (primitive.ObjectID, error) {
12 | if id, ok := result.InsertedID.(primitive.ObjectID); ok {
13 | return id, nil
14 | } else {
15 |
16 | return primitive.NilObjectID, errors.New("failed converting")
17 | }
18 |
19 | }
20 |
21 | func ObjectIDFromHex(hexString string) (primitive.ObjectID, error){
22 | objID, err := primitive.ObjectIDFromHex(hexString)
23 | return objID, err
24 | }
--------------------------------------------------------------------------------
/models/broker.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import "sync"
4 |
5 | // struct representing client connections
6 | type Broker struct {
7 | // Registered Clients.
8 | Clients map[*Client]bool
9 |
10 | // messages from the Clients.
11 | Notification chan *ChatEvent
12 |
13 | // Register requests from the Clients.
14 | Join chan *Client
15 |
16 | // Unregister requests from Clients.
17 | Leave chan *Client
18 |
19 | Room *ChatRoom
20 |
21 | Mutex sync.Mutex
22 | }
23 |
24 | func NewBroker(room *ChatRoom) *Broker {
25 | return &Broker{
26 | Notification: make(chan *ChatEvent, 100),
27 | Join: make(chan *Client),
28 | Leave: make(chan *Client),
29 | Clients: make(map[*Client]bool),
30 | Room: room,
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/models/chat_event.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "time"
5 |
6 | "go.mongodb.org/mongo-driver/bson/primitive"
7 | )
8 |
9 | const (
10 | // Subscribe is used to broadcast a message indicating user has joined ChatRoom
11 | Subscribe = "join"
12 | // Broadcast is used to broadcast messages to all subscribed users
13 | Broadcast = "send"
14 | // Unsubscribe is used to broadcast a message indicating user has left ChatRoom
15 | Unsubscribe = "leave"
16 | )
17 |
18 | // struct representing a message event in an ChatRoom
19 | type ChatEvent struct {
20 | ID primitive.ObjectID `json:"_id" bson:"_id,omitempty"`
21 | EventType string `json:"type" bson:"type ,omitempty"`
22 | UserID primitive.ObjectID `json:"user_id,omitempty"`
23 | RoomID primitive.ObjectID `json:"room_id,omitempty"`
24 | Message string `json:"message,omitempty"`
25 | Timestamp time.Time `json:"time"`
26 | }
27 |
--------------------------------------------------------------------------------
/controllers/auth_api.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "net/http"
5 |
6 | "chicko_chat/models"
7 | "github.com/gin-gonic/gin"
8 | )
9 |
10 |
11 |
12 | func (c *Controller) StartConversationApi(ctx *gin.Context) {
13 | // Validate input
14 | var user *data.UserData
15 | if err := ctx.ShouldBindJSON(&user); err != nil {
16 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
17 | return
18 | }
19 |
20 | // Search For existing user
21 | usr, err := c.DB.FindByEmail(user.Email)
22 | if err == nil{
23 | if usr.Name == "" {
24 | usr.Name = user.Name
25 | usr , err = c.DB.UpdateUserName(usr)
26 | if err != nil {
27 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
28 | return
29 | }
30 |
31 | }
32 |
33 | ctx.JSON(http.StatusOK, gin.H{"data": usr})
34 | return
35 |
36 | }
37 |
38 | id, err := c.DB.AddUser(user)
39 | if err != nil {
40 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
41 | return
42 |
43 | }
44 | user.ID = id
45 | ctx.JSON(http.StatusOK, gin.H{"data": user})
46 |
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/controllers/user_api.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gin-gonic/gin"
7 | "chicko_chat/models"
8 | )
9 |
10 | func (c *Controller) GetUserRoomsApi(ctx *gin.Context) {
11 | // Validate input
12 | var user *data.UserData
13 | if err := ctx.ShouldBindJSON(&user); err != nil {
14 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
15 | return
16 | }
17 |
18 | var rooms []data.ChatRoom
19 | // Search For rooms
20 | rooms, err := c.DB.GetHistoryOfUser(user)
21 | if err != nil {
22 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
23 | return
24 |
25 | }
26 | ctx.JSON(http.StatusOK, gin.H{"data": rooms})
27 | }
28 |
29 | func (c *Controller) GetUserDetailsRoomApi(ctx *gin.Context) {
30 | // Validate input
31 | var room *data.ChatRoom
32 | if err := ctx.ShouldBindJSON(&room); err != nil {
33 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
34 | return
35 | }
36 |
37 | var users []data.UserData
38 | // Search For rooms
39 | users, err := c.DB.GetUserData(room.Clients)
40 | if err != nil {
41 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
42 | return
43 |
44 | }
45 | ctx.JSON(http.StatusOK, gin.H{"users": users})
46 | }
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/models/client.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/gorilla/websocket"
7 | "go.mongodb.org/mongo-driver/bson/primitive"
8 | )
9 |
10 | const (
11 | // Max wait time when writing message to peer
12 | writeWait = 10 * time.Second
13 |
14 | // Max time till next pong from peer
15 | pongWait = 60 * time.Second
16 |
17 | // Send ping interval, must be less then pong wait time
18 | pingPeriod = (pongWait * 9) / 10
19 |
20 | // Maximum message size allowed from peer.
21 | maxMessageSize = 10000
22 | )
23 |
24 | // struct representing a user in a ChatRoom
25 | type Client struct {
26 | User UserData
27 | LastActivity time.Time `json:"last_activity"`
28 | // The websocket Connection.
29 | Conn *websocket.Conn `json:"-"`
30 | // Buffered channel of outbound messages.
31 | Send chan *ChatEvent `json:"-"`
32 | // Broker for connection
33 | Broker *Broker
34 | }
35 |
36 | type UserData struct {
37 | ID primitive.ObjectID `json:"_id" bson:"_id,omitempty"`
38 | Email string `json:"email" bson:"email" binding:"required"`
39 | Name string `json:"name" bson:"name"`
40 | Active bool `json:"active" bson:"active"`
41 | }
42 |
43 | func NewClient(conn *websocket.Conn, user *UserData, broker *Broker) *Client {
44 |
45 | client := &Client{
46 | User: *user,
47 | Conn: conn,
48 | Send: make(chan *ChatEvent, 100),
49 | Broker: broker,
50 | }
51 | return client
52 | }
53 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # Chicko Chat
2 |
3 |
4 | Chicko chat is a real time multi room multi user chat using golang, websocket , mongodb with restful api
5 |
6 | [ Sample Front ](https://github.com/younes-nb/chicko-frontend)
7 |
8 |
9 |
10 | ## Install ##
11 | All that you need is [Golang](https://golang.org/).
12 | ```sh
13 | $ git clone https://github.com/oldcorvus/chicko_chat.git
14 |
15 | ```
16 | ```sh
17 | $ go mod tidy
18 |
19 | ```
20 | ```sh
21 | $ go build -o chat
22 |
23 | ```
24 | ```sh
25 | $ ./chat --mongoURI="mongodb://localhost:27017"
26 |
27 | ```
28 | And navigate to `http://127.0.0.1:8080/`.
29 |
30 |
31 | ## Running Locally with Docker
32 |
33 | 1.build the image:
34 |
35 | ```sh
36 | $ docker-compose build .
37 | ```
38 | 2.Spin up the containers
39 |
40 | ```sh
41 | $ docker-compose up
42 | ```
43 | And navigate to `http://127.0.0.1:8080/`.
44 |
45 | ## ChatRoom API ##
46 |
47 | * `GET /chats/`: to join a room and start chat
48 | * `GET /ws/:roomId/:userId/`: to start a websocket connection
49 |
50 | * `POST /start/`: register and obtain user id
51 | * `POST /user-rooms/`: ro get user rooms based on user id
52 | * `POST /room-history/`: to retrieve messages of room based on room id
53 | * `POST /add-user-room/`: to register user in room
54 | * `POST /room-user-details/`: to retrieve user data of a room
55 |
56 | ## Test ##
57 |
58 | to run tests
59 |
60 | Controllers :
61 | ```sh
62 | $ cd controllers
63 |
64 | ```
65 | ```sh
66 | $ go test
67 |
68 | ```
69 | Database
70 |
71 | ```sh
72 | $ cd database
73 |
74 | ```
75 | ```sh
76 | $ go test
77 |
78 | ```
79 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "chicko_chat/controllers"
5 | "chicko_chat/database"
6 | "chicko_chat/models"
7 | "chicko_chat/websocket"
8 | "github.com/gin-contrib/cors"
9 |
10 | "flag"
11 | "net/http"
12 |
13 | "github.com/gin-gonic/gin"
14 | )
15 |
16 | func main() {
17 | mongoURI := flag.String("mongoURI", "mongodb://mongo:27017", "Database hostname url")
18 | enableCredentials := flag.Bool("enableCredentials", false, "Enable the use of credentials for mongo connection")
19 | flag.Parse()
20 |
21 | db := database.ConnectDatabse(*mongoURI, *enableCredentials)
22 | router := gin.Default()
23 | controller := controllers.Controller{
24 | DB: db,
25 | }
26 | manager := &websocket.BrokerManager{
27 | Brokers: make(map[*data.Broker]bool),
28 | DB: db,
29 | }
30 | websocketServer := &websocket.WsServer{
31 | Manager: manager,
32 | }
33 | router.LoadHTMLGlob("templates/*")
34 | router.GET("/", func(c *gin.Context) {
35 |
36 | c.HTML(http.StatusOK, "index.tmpl", gin.H{
37 | "title": "Sample Front",
38 | "name": "Moel",
39 | })
40 | })
41 | router.Use(cors.Default())
42 | router.GET("/chat/", controller.JoinRoom)
43 | router.POST("/start/", controller.StartConversationApi)
44 | router.POST("/user-rooms/", controller.GetUserRoomsApi)
45 | router.POST("/create-room/", controller.CreateRoomApi)
46 | router.POST("/room-history/", controller.RoomHistoryApi)
47 | router.POST("/add-user-room/", controller.AddUserToRoomApi)
48 | router.POST("/room-user-details/", controller.GetUserDetailsRoomApi)
49 | router.GET("/ws/:roomId/:userId/", func(c *gin.Context) {
50 | roomId := c.Param("roomId")
51 | userId := c.Param("userId")
52 | websocketServer.ServeWs(c.Writer, c.Request, roomId, userId)
53 | })
54 | router.Run()
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/websocket/wsServer.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "chicko_chat/database"
5 |
6 | "log"
7 | "net/http"
8 | "chicko_chat/models"
9 | "github.com/gorilla/websocket"
10 | "go.mongodb.org/mongo-driver/bson/primitive"
11 | )
12 |
13 | const (
14 | socketBufferSize = 1024
15 | messageBufferSize = 256
16 | )
17 |
18 | var upgrader = &websocket.Upgrader{ReadBufferSize: socketBufferSize,
19 | WriteBufferSize: socketBufferSize,
20 | CheckOrigin: func(r *http.Request) bool {
21 | //origin := r.Header.Get("Origin")
22 | return true
23 | },}
24 |
25 | type WsServer struct {
26 | Manager *BrokerManager
27 | }
28 |
29 | func (server *WsServer) findBrokerbyRoomID(ID primitive.ObjectID) *data.Broker {
30 | for broker := range server.Manager.Brokers {
31 | if broker.Room.ID == ID {
32 | return broker
33 | }
34 | }
35 | return nil
36 | }
37 |
38 | func (server *WsServer) createBroker(room *data.ChatRoom) *data.Broker {
39 | broker := data.NewBroker(room)
40 | go server.Manager.RunBroker(broker)
41 | server.Manager.Brokers[broker] = true
42 |
43 | return broker
44 | }
45 |
46 | func (server *WsServer) ServeWs(w http.ResponseWriter, req *http.Request, roomId string, userId string) {
47 | socket, err := upgrader.Upgrade(w, req, nil)
48 | if err != nil {
49 | log.Fatal("ServeHTTP:", err)
50 | return
51 | }
52 | roomID, err := database.ObjectIDFromHex(roomId)
53 | userID, err := database.ObjectIDFromHex(userId)
54 |
55 | if err != nil {
56 | return
57 | }
58 | user := &data.UserData{
59 | ID: userID,
60 | }
61 |
62 | room := &data.ChatRoom{
63 | ID: roomID,
64 | }
65 | broker := server.findBrokerbyRoomID(room.ID)
66 | if broker == nil {
67 | broker = server.createBroker(room)
68 | }
69 | client := data.NewClient(socket, user, broker)
70 | clientManager := clientManager{
71 | client: client,
72 | }
73 | broker.Clients[client] = true
74 | broker.Join <- client
75 | defer func() { broker.Leave <- client }()
76 | go clientManager.clientWrite()
77 | clientManager.clientRead()
78 | }
79 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module chicko_chat
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/gin-contrib/cors v1.4.0
7 | github.com/gin-gonic/gin v1.9.0
8 | github.com/gorilla/websocket v1.5.0
9 | github.com/stretchr/testify v1.8.2
10 | go.mongodb.org/mongo-driver v1.11.2
11 | )
12 |
13 | require (
14 | github.com/bytedance/sonic v1.8.0 // indirect
15 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
16 | github.com/davecgh/go-spew v1.1.1 // indirect
17 | github.com/gin-contrib/sse v0.1.0 // indirect
18 | github.com/go-playground/locales v0.14.1 // indirect
19 | github.com/go-playground/universal-translator v0.18.1 // indirect
20 | github.com/go-playground/validator/v10 v10.11.2 // indirect
21 | github.com/goccy/go-json v0.10.0 // indirect
22 | github.com/golang/snappy v0.0.1 // indirect
23 | github.com/json-iterator/go v1.1.12 // indirect
24 | github.com/klauspost/compress v1.13.6 // indirect
25 | github.com/klauspost/cpuid/v2 v2.0.9 // indirect
26 | github.com/leodido/go-urn v1.2.1 // indirect
27 | github.com/mattn/go-isatty v0.0.17 // indirect
28 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
29 | github.com/modern-go/reflect2 v1.0.2 // indirect
30 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
31 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect
32 | github.com/pkg/errors v0.9.1 // indirect
33 | github.com/pmezard/go-difflib v1.0.0 // indirect
34 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
35 | github.com/ugorji/go/codec v1.2.9 // indirect
36 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect
37 | github.com/xdg-go/scram v1.1.1 // indirect
38 | github.com/xdg-go/stringprep v1.0.3 // indirect
39 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
40 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
41 | golang.org/x/crypto v0.5.0 // indirect
42 | golang.org/x/net v0.7.0 // indirect
43 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
44 | golang.org/x/sys v0.5.0 // indirect
45 | golang.org/x/text v0.7.0 // indirect
46 | google.golang.org/protobuf v1.28.1 // indirect
47 | gopkg.in/yaml.v3 v3.0.1 // indirect
48 | )
49 |
--------------------------------------------------------------------------------
/database/connect_database.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import (
4 | "context"
5 | "log"
6 | "time"
7 |
8 | "go.mongodb.org/mongo-driver/mongo/readpref"
9 |
10 | "chicko_chat/models"
11 | "os"
12 | "go.mongodb.org/mongo-driver/mongo"
13 | "go.mongodb.org/mongo-driver/mongo/options"
14 | )
15 |
16 | func ConnectDatabseTest() *ChatDatabase {
17 |
18 | // Create mongo client configuration
19 | co := options.Client().ApplyURI("mongodb://localhost:27017")
20 |
21 | // Establish database connection
22 | client, err := mongo.NewClient(co)
23 | if err != nil {
24 | log.Fatal(err)
25 | }
26 | ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
27 | defer cancel()
28 |
29 | err = client.Connect(ctx)
30 |
31 | if err != nil {
32 | log.Fatal(err)
33 | }
34 |
35 | db := &ChatDatabase{
36 | Users: client.Database("chicko_chat").Collection("users_test"),
37 | Rooms: client.Database("chicko_chat").Collection("rooms_test"),
38 | Messages: client.Database("chicko_chat").Collection("message_test"),
39 | }
40 | defer func() {
41 | if err = db.Users.Drop(context.TODO()); err != nil {
42 | log.Fatal(err)
43 | }
44 | if err = db.Rooms.Drop(context.TODO()); err != nil {
45 | log.Fatal(err)
46 | }
47 | if err = db.Messages.Drop(context.TODO()); err != nil {
48 | log.Fatal(err)
49 | }
50 | }()
51 | room := &data.ChatRoom{
52 | Title: "Data For Test",
53 | }
54 |
55 | _, err = db.Rooms.InsertOne(context.TODO(), room)
56 |
57 | if err != nil {
58 | log.Fatal(err)
59 | }
60 | return db
61 |
62 | }
63 | func ConnectDatabse(mongoURI string, enableCredentials bool ) *ChatDatabase {
64 | // Create mongo client configuration
65 | co := options.Client().ApplyURI(mongoURI)
66 | if enableCredentials {
67 | co.Auth = &options.Credential{
68 | Username: os.Getenv("MONGODB_USERNAME"),
69 | Password: os.Getenv("MONGODB_PASSWORD"),
70 | }
71 | }
72 | // Establish database connection
73 | client, err := mongo.NewClient(co)
74 | if err != nil {
75 | log.Fatal(err)
76 | }
77 | ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
78 | defer cancel()
79 |
80 | err = client.Connect(ctx)
81 | if err != nil {
82 | log.Fatal(err)
83 | }
84 |
85 |
86 | if err := client.Ping(context.TODO(), readpref.Primary()); err != nil {
87 | panic(err)
88 | }
89 | db := &ChatDatabase{
90 | Users: client.Database("chicko_chat").Collection("users"),
91 | Messages: client.Database("chicko_chat").Collection("messages"),
92 | Rooms: client.Database("chicko_chat").Collection("rooms"),
93 | }
94 |
95 | log.Printf("Database connection established")
96 |
97 | return db
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/controllers/room_api.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gin-gonic/gin"
7 | "chicko_chat/models"
8 | "chicko_chat/database"
9 | "go.mongodb.org/mongo-driver/bson/primitive"
10 | "time"
11 | )
12 |
13 | func (c *Controller) CreateRoomApi(ctx *gin.Context) {
14 | // Validate input
15 | var room *data.ChatRoom
16 | if err := ctx.ShouldBindJSON(&room); err != nil {
17 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
18 | return
19 | }
20 | // Create room
21 | room.CreatedAt = time.Now()
22 | room, err := c.DB.CreateRoom(room)
23 | if err != nil {
24 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
25 | return
26 | }
27 | ctx.JSON(http.StatusOK, gin.H{"data": room})
28 |
29 | }
30 |
31 |
32 | func (c *Controller) RoomHistoryApi(ctx *gin.Context) {
33 | // Validate input
34 | var room *data.ChatRoom
35 | if err := ctx.ShouldBindJSON(&room); err != nil {
36 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
37 | return
38 | }
39 | var messages []data.ChatEvent
40 | messages, err := c.DB.GetHistoryOfRoom(room)
41 | if err != nil {
42 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
43 | return
44 | }
45 | ctx.JSON(http.StatusOK, gin.H{"data": messages})
46 | }
47 |
48 | func (c *Controller) AddUserToRoomApi(ctx *gin.Context) {
49 | // Validate input
50 | var room *data.ChatRoom
51 | if err := ctx.ShouldBindJSON(&room); err != nil {
52 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
53 | return
54 | }
55 | room, err := c.DB.AddClientToRoom(room)
56 | if err != nil {
57 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
58 | return
59 | }
60 | ctx.JSON(http.StatusOK, gin.H{"data": room})
61 | }
62 |
63 |
64 |
65 | func (c *Controller) JoinRoom(ctx *gin.Context) {
66 | // Validate input
67 | var room *data.ChatRoom
68 | roomId := ctx.Query("roomId")
69 | userId := ctx.Query("userId")
70 | id , err := database.ObjectIDFromHex(roomId)
71 | userID, err := database.ObjectIDFromHex(userId)
72 | room , err = c.DB.FindRoomByID(id )
73 | if err != nil {
74 | ctx.JSON(http.StatusBadRequest, gin.H{"error": "room not found"})
75 | return
76 | }
77 | var found bool = false
78 | for i := range room.Clients {
79 | if room.Clients[i] == userID {
80 | found = true
81 | }
82 | }
83 | if found != true {
84 | room , err = c.DB.AddClientToRoom(&data.ChatRoom{
85 | ID : id,
86 | Clients : []primitive.ObjectID{userID},
87 | })
88 | }
89 |
90 | if err != nil {
91 | ctx.JSON(http.StatusBadRequest, gin.H{"error": "room not found"})
92 | return
93 | }
94 |
95 | ctx.HTML(http.StatusOK, "chat.tmpl", gin.H{
96 | "title": "Sample Front",
97 | "name": "Moel",
98 | "roomId" : roomId,
99 | "room" : room,
100 | "userId": userId,
101 | })
102 |
103 | }
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/templates/chat.tmpl:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{.title}}
4 |
10 |
11 |
12 | {{.name}}
13 | Room ID {{ .roomId}}
14 | room title : {{ .room.Title}}
15 |
16 |
17 |
18 |
22 |
23 |
24 |
152 |
153 |
154 |
155 |
--------------------------------------------------------------------------------
/database/mongo.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import (
4 | "chicko_chat/models"
5 | "context"
6 | "errors"
7 | "go.mongodb.org/mongo-driver/bson"
8 | "go.mongodb.org/mongo-driver/bson/primitive"
9 | "go.mongodb.org/mongo-driver/mongo"
10 | "go.mongodb.org/mongo-driver/mongo/options"
11 | "time"
12 | )
13 |
14 | type ChatDatabase struct {
15 | Users *mongo.Collection
16 |
17 | Messages *mongo.Collection
18 |
19 | Rooms *mongo.Collection
20 | }
21 |
22 |
23 | func (c *ChatDatabase) AddUser(user *data.UserData) (primitive.ObjectID, error) {
24 |
25 | res, err := c.Users.InsertOne(context.TODO(), user)
26 |
27 | if err != nil {
28 | return primitive.NilObjectID, err
29 | }
30 |
31 | return convertId(res)
32 |
33 | }
34 |
35 | // FindByEmail will be used to find a new user registry by email
36 | func (c *ChatDatabase) FindByEmail(email string) (*data.UserData, error) {
37 |
38 | // Find user by email
39 | var user = data.UserData{}
40 | err := c.Users.FindOne(context.TODO(), bson.M{"email": email}).Decode(&user)
41 |
42 | if err != nil {
43 | // Checks if the user was not found
44 | if err == mongo.ErrNoDocuments {
45 | return nil, errors.New("user not found")
46 | }
47 | return nil, err
48 | }
49 |
50 | return &user, nil
51 |
52 | }
53 |
54 |
55 | func (c *ChatDatabase) CreateRoom(room *data.ChatRoom) (*data.ChatRoom, error) {
56 |
57 | res, err := c.Rooms.InsertOne(context.TODO(), room)
58 |
59 | if err != nil {
60 | return room, err
61 | }
62 |
63 | room.ID, err = convertId(res)
64 |
65 | if err != nil {
66 | return room, err
67 | }
68 | return room, nil
69 |
70 | }
71 |
72 |
73 | func (c *ChatDatabase) UpdateUserName(user *data.UserData) (*data.UserData, error) {
74 | filter := bson.D{{"_id", user.ID}}
75 | update := bson.D{{"$set", bson.D{{"name",user.Name}}}}
76 | _, err := c.Users.UpdateOne(context.TODO(), filter, update)
77 | if err != nil {
78 | return nil, err
79 | }
80 | usr := &data.UserData{}
81 | err = c.Users.FindOne(context.TODO(),filter).Decode(&usr)
82 |
83 | if err != nil {
84 | return nil, err
85 | }
86 | return usr, nil
87 | }
88 |
89 | func (c *ChatDatabase) AddClientToRoom(room *data.ChatRoom) (*data.ChatRoom, error) {
90 | change := bson.M{
91 | "$push": bson.M{
92 | "users": bson.M{"$each":room.Clients},
93 | },
94 | }
95 | filter := bson.M{
96 | "_id": room.ID,
97 | }
98 |
99 | _, err := c.Rooms.UpdateOne(context.Background(), filter, change)
100 | if err != nil {
101 | return nil, err
102 | }
103 |
104 | rm := &data.ChatRoom{}
105 | err = c.Rooms.FindOne(context.TODO(),filter).Decode(&rm)
106 |
107 | if err != nil {
108 | return nil, err
109 | }
110 | return rm, nil
111 |
112 | }
113 |
114 | func (c *ChatDatabase) SaveMessage(message *data.ChatEvent) (primitive.ObjectID, error) {
115 | message.Timestamp = time.Now()
116 |
117 | res, err := c.Messages.InsertOne(context.TODO(), message)
118 |
119 | if err != nil {
120 | return primitive.NilObjectID, err
121 | }
122 |
123 | return convertId(res)
124 | }
125 |
126 |
127 | // Get history of chat from the databse
128 | func (c *ChatDatabase) GetHistoryOfRoom(room *data.ChatRoom) ([]data.ChatEvent, error) {
129 |
130 | findOptions := options.Find()
131 | cur, err := c.Messages.Find(context.TODO(), bson.D{{"roomid", room.ID}}, findOptions)
132 | if err != nil {
133 | return nil, err
134 | }
135 | defer cur.Close(context.TODO())
136 |
137 | var messages []data.ChatEvent
138 | err = cur.All(context.TODO(), &messages)
139 |
140 | return messages, nil
141 | }
142 |
143 | // Get chat rooms of user
144 | func (c *ChatDatabase) GetHistoryOfUser(user *data.UserData) ([]data.ChatRoom, error) {
145 |
146 | findOptions := options.Find()
147 | cur, err := c.Rooms.Find(context.TODO(), bson.M{"users": bson.M{"$in":[]primitive.ObjectID{user.ID}}}, findOptions)
148 | if err != nil {
149 | return nil, err
150 | }
151 | defer cur.Close(context.TODO())
152 |
153 | var rooms []data.ChatRoom
154 | err = cur.All(context.TODO(), &rooms)
155 |
156 | return rooms, nil
157 | }
158 |
159 |
160 | // Get user detail
161 | func (c *ChatDatabase) GetUserData(ids []primitive.ObjectID) ([]data.UserData, error) {
162 |
163 | findOptions := options.Find()
164 | cur, err := c.Users.Find(context.TODO(), bson.M{"_id": bson.M{"$in":ids}}, findOptions)
165 | if err != nil {
166 | return nil, err
167 | }
168 | defer cur.Close(context.TODO())
169 | var users []data.UserData
170 | err = cur.All(context.TODO(), &users)
171 | return users, nil
172 | }
173 |
174 |
175 |
176 | func (c *ChatDatabase) FindRoomByID(id primitive.ObjectID) (*data.ChatRoom, error) {
177 |
178 | // Find room by id
179 | var room = data.ChatRoom{}
180 | err := c.Rooms.FindOne(context.TODO(), bson.M{"_id": id}).Decode(&room)
181 |
182 | if err != nil {
183 | // Checks if the room was not found
184 | if err == mongo.ErrNoDocuments {
185 | return nil, errors.New("room not found")
186 | }
187 | return nil, err
188 | }
189 |
190 | return &room, nil
191 |
192 | }
193 |
--------------------------------------------------------------------------------
/websocket/manager.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "chicko_chat/database"
5 | data "chicko_chat/models"
6 | "log"
7 |
8 | "fmt"
9 | "time"
10 |
11 | "github.com/gorilla/websocket"
12 | )
13 |
14 | const (
15 | // Max wait time when writing message to peer
16 | writeWait = 10 * time.Second
17 |
18 | // Max time till next pong from peer
19 | pongWait = 60 * time.Second
20 |
21 | // Send ping interval, must be less then pong wait time
22 | pingPeriod = (pongWait * 9) / 10
23 |
24 | // Maximum message size allowed from peer.
25 | maxMessageSize = 10000
26 | )
27 |
28 | // the amount of time to wait when pushing a message to
29 | // a slow client or a client that closed after `range Clients` started.
30 | const patience time.Duration = time.Second * 1
31 |
32 | type BrokerManager struct {
33 | Brokers map[*data.Broker]bool
34 | DB *database.ChatDatabase
35 | }
36 |
37 | type clientManager struct {
38 | client *data.Client
39 | }
40 |
41 | // runs broker accepting various requests
42 | func (manager *BrokerManager) RunBroker(broker *data.Broker) {
43 | for {
44 | select {
45 | case client := <-broker.Join:
46 | manager.registerClient(client, broker)
47 |
48 | case client := <-broker.Leave:
49 | manager.unregisterClient(client, broker)
50 |
51 | case message := <-broker.Notification:
52 | manager.broadcastToClients(message, broker)
53 | }
54 |
55 | }
56 | }
57 |
58 | func (manager *BrokerManager) registerClient(client *data.Client, broker *data.Broker) {
59 | broker.Mutex.Lock()
60 | broker.Clients[client] = true
61 | broker.Mutex.Unlock()
62 | message := data.ChatEvent{
63 | EventType: data.Subscribe,
64 | RoomID: broker.Room.ID,
65 | UserID: client.User.ID,
66 | }
67 | broker.Notification <- &message
68 |
69 | log.Printf("Client added. %d registered Clients", len(broker.Clients))
70 |
71 | }
72 |
73 | func (manager *BrokerManager) unregisterClient(client *data.Client, broker *data.Broker) {
74 | broker.Mutex.Lock()
75 | if _, ok := broker.Clients[client]; ok {
76 | delete(broker.Clients, client)
77 | close(client.Send)
78 | }
79 | broker.Mutex.Unlock()
80 |
81 | message := data.ChatEvent{
82 | EventType: data.Unsubscribe,
83 | RoomID: broker.Room.ID,
84 | UserID: client.User.ID,
85 | }
86 | broker.Notification <- &message
87 |
88 | log.Printf("Removed client. %d registered Clients", len(broker.Clients))
89 |
90 | }
91 |
92 | func (manager *BrokerManager) broadcastToClients(message *data.ChatEvent, broker *data.Broker) {
93 | message.Timestamp = time.Now()
94 | msg, err := manager.DB.SaveMessage(message)
95 | if err != nil {
96 | log.Print("message not sent: " + msg.Hex())
97 |
98 | }
99 | broker.Mutex.Lock()
100 |
101 | for client := range broker.Clients {
102 | select {
103 | case client.Send <- message:
104 | log.Print("message sent to: " + client.User.ID.Hex())
105 | case <-time.After(patience):
106 | log.Print("Skipping client: " + client.User.ID.Hex())
107 | default:
108 | log.Print("Deleting client: " + client.User.ID.Hex())
109 | close(client.Send)
110 | delete(broker.Clients, client)
111 | }
112 | }
113 | broker.Mutex.Unlock()
114 |
115 | }
116 |
117 | func (manager *clientManager) clientRead() {
118 | defer func() {
119 | manager.ClientDisconnect()
120 | }()
121 |
122 | manager.client.Conn.SetReadLimit(maxMessageSize)
123 | manager.client.Conn.SetReadDeadline(time.Now().Add(pongWait))
124 | manager.client.Conn.SetPongHandler(func(string) error { manager.client.Conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
125 |
126 | // Start endless read loop, waiting for messages from client
127 | for {
128 | var msg data.ChatEvent
129 | // Read in a new message as JSON and map it to a Message object
130 | err := manager.client.Conn.ReadJSON(&msg)
131 |
132 | if err != nil {
133 | if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
134 | log.Printf("unexpected close error: %v", err)
135 | }
136 | break
137 | }
138 | // handel message
139 | manager.handleNewMessage(&msg)
140 | }
141 |
142 | }
143 |
144 | func (manager *clientManager) clientWrite() {
145 | ticker := time.NewTicker(pingPeriod)
146 | defer func() {
147 | ticker.Stop()
148 | manager.client.Conn.Close()
149 | }()
150 | for {
151 | select {
152 | case message, ok := <-manager.client.Send:
153 | manager.client.Conn.SetWriteDeadline(time.Now().Add(writeWait))
154 | if !ok {
155 | // The WsServer closed the channel.
156 | manager.client.Conn.WriteMessage(websocket.CloseMessage, []byte{})
157 | return
158 | }
159 |
160 | manager.client.Conn.WriteJSON(message)
161 |
162 | case <-ticker.C:
163 | manager.client.Conn.SetWriteDeadline(time.Now().Add(writeWait))
164 | if err := manager.client.Conn.WriteMessage(websocket.PingMessage, nil); err != nil {
165 | return
166 | }
167 | }
168 | }
169 | }
170 |
171 | func (manager *clientManager) ClientDisconnect() {
172 | manager.client.Conn.Close()
173 | }
174 |
175 | func (manager *clientManager) handleNewMessage(message *data.ChatEvent) {
176 | fmt.Println(message)
177 | switch message.EventType {
178 | case data.Broadcast:
179 | manager.client.Broker.Notification <- message
180 |
181 | case data.Subscribe, data.Unsubscribe:
182 | manager.notifyJoinedLeft(message)
183 |
184 | }
185 |
186 | }
187 |
188 | func (manager *clientManager) notifyJoinedLeft(message *data.ChatEvent) {
189 |
190 | manager.client.Send <- message
191 | }
192 |
--------------------------------------------------------------------------------
/templates/index.tmpl:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ .title }}
4 |
10 |
11 |
12 | {{.name}}
13 |
14 |
15 |
19 |
24 |
29 | Your chat history
30 |
31 |
32 |
33 |
148 |
149 |
150 |
--------------------------------------------------------------------------------
/database/mongo_test.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import (
4 | "chicko_chat/models"
5 | "context"
6 | "go.mongodb.org/mongo-driver/bson"
7 | "go.mongodb.org/mongo-driver/bson/primitive"
8 | "testing"
9 | )
10 |
11 | func TestFindByEmail(t *testing.T) {
12 |
13 | db := ConnectDatabseTest()
14 | //test for not existing email
15 | _, err := db.FindByEmail("moelcrow@gmail.com")
16 | if err == nil {
17 | t.Fatalf("user found!")
18 | }
19 |
20 | //adding user to databse
21 | db.Users.InsertOne(context.TODO(), bson.M{"email": "moelcrow@gmail.com"})
22 |
23 | _, err = db.FindByEmail("moelcrow@gmail.com")
24 | if err != nil {
25 | t.Fatalf("error user not found")
26 | }
27 |
28 | }
29 | func TestFindRoomByID(t *testing.T) {
30 |
31 | db := ConnectDatabseTest()
32 | id, err := primitive.ObjectIDFromHex("640778694829658eebc2d55d")
33 |
34 | _, err = db.FindRoomByID(id)
35 | if err == nil {
36 | t.Fatalf("room found!")
37 | }
38 |
39 | //adding room to databse
40 | db.Rooms.InsertOne(context.TODO(), bson.M{"_id": id})
41 |
42 | _, err = db.FindRoomByID(id)
43 | if err != nil {
44 | t.Fatalf("error room not found")
45 | }
46 |
47 | }
48 |
49 | func TestAddUser(t *testing.T) {
50 |
51 | db := ConnectDatabseTest()
52 | user := &data.UserData{
53 | Email: "testregister@gmail.com",
54 | Name: "test user",
55 | Active: true,
56 | }
57 |
58 | _, err := db.AddUser(user)
59 |
60 | if err != nil {
61 | t.Fatalf("failure in adding user data to databse")
62 | }
63 | var usr = data.UserData{}
64 | res := db.Users.FindOne(context.TODO(), bson.M{"email": user.Email}).Decode(&usr)
65 |
66 | if res != nil {
67 | t.Fatalf("failure finding added user ")
68 | }
69 |
70 | }
71 |
72 | func TestCreateRoom(t *testing.T) {
73 |
74 | db := ConnectDatabseTest()
75 | room := &data.ChatRoom{
76 | Title: "Test",
77 | }
78 |
79 | _, err := db.CreateRoom(room)
80 |
81 | if err != nil {
82 | t.Fatalf("failure in adding room data to database")
83 | }
84 | var rm = data.ChatRoom{}
85 | res := db.Rooms.FindOne(context.TODO(), bson.M{"title": room.Title}).Decode(&rm)
86 |
87 | if res != nil {
88 | t.Fatalf("failure finding added room ")
89 | }
90 |
91 | }
92 |
93 | func TestAddClientToRoom(t *testing.T) {
94 |
95 | db := ConnectDatabseTest()
96 | room := &data.ChatRoom{
97 | Title: "Data For Test",
98 | Clients: []primitive.ObjectID{},
99 | }
100 |
101 | _, err := db.Rooms.InsertOne(context.TODO(), room)
102 |
103 | err = db.Rooms.FindOne(context.TODO(), bson.M{"title": "Data For Test"}).Decode(&room)
104 | if err != nil {
105 | t.Fatalf("failure finding added room ")
106 | }
107 |
108 | user := &data.UserData{
109 | Email: "testregister@gmail.com",
110 | Name: "test user",
111 | Active: true,
112 | }
113 | db.Users.InsertOne(context.TODO(), user)
114 | err = db.Users.FindOne(context.TODO(), user).Decode(&user)
115 | if err != nil {
116 | t.Fatalf("failure finding added user ")
117 |
118 | }
119 | room.Clients = append(room.Clients, user.ID)
120 |
121 | res, err := db.AddClientToRoom(room)
122 | if err != nil {
123 | t.Fatalf("failure adding user into room ")
124 |
125 | }
126 | if res.Clients[0] != user.ID {
127 | t.Fatalf("incorect user data ")
128 |
129 | }
130 | }
131 |
132 | func TestAddMessage(t *testing.T) {
133 |
134 | db := ConnectDatabseTest()
135 | message := &data.ChatEvent{
136 | EventType: data.Broadcast,
137 | UserID: primitive.NewObjectID(),
138 | RoomID: primitive.NewObjectID(),
139 | Message: "test message",
140 | }
141 |
142 | res, err := db.SaveMessage(message)
143 |
144 | if err != nil {
145 | t.Fatalf("failure in adding message data to databse")
146 | }
147 | msg := &data.ChatEvent{}
148 | err = db.Messages.FindOne(context.TODO(), bson.M{"_id": res}).Decode(&msg)
149 |
150 | if err != nil || msg.ID != res {
151 | t.Fatalf("failure finding added message ")
152 | }
153 |
154 | }
155 |
156 | func TestGetHitoryOfRoom(t *testing.T) {
157 |
158 | db := ConnectDatabseTest()
159 | var messages []interface{}
160 | id, err := primitive.ObjectIDFromHex("640778694829658eebc2d55b")
161 |
162 | room := &data.ChatRoom{
163 | ID: id,
164 | Title: "test",
165 | }
166 |
167 | for i := 1; i < 5; i++ {
168 | message := data.ChatEvent{
169 | EventType: data.Broadcast,
170 | UserID: primitive.NewObjectID(),
171 | RoomID: id,
172 | Message: "test message",
173 | }
174 | message2 := data.ChatEvent{
175 | EventType: data.Broadcast,
176 | UserID: primitive.NewObjectID(),
177 | RoomID: primitive.NewObjectID(),
178 | Message: "another room",
179 | }
180 | messages = append(messages, message)
181 | messages = append(messages, message2)
182 |
183 | }
184 | _, err = db.Messages.InsertMany(context.TODO(), messages)
185 | if err != nil {
186 | t.Fatalf("failure in adding messages data to databse")
187 | }
188 | var result []data.ChatEvent
189 | result, err = db.GetHistoryOfRoom(room)
190 | if err != nil || len(result) != 4 {
191 | t.Fatalf("failure in retriveing messages data from databse")
192 | }
193 |
194 | }
195 |
196 | func TestHistoryOfUser(t *testing.T) {
197 |
198 | db := ConnectDatabseTest()
199 | var rooms []interface{}
200 | id, err := primitive.ObjectIDFromHex("640778694829658eebc2d55b")
201 |
202 | user := &data.UserData{
203 | ID: id,
204 | Email: "testuser@mail.com",
205 | }
206 |
207 | for i := 1; i < 5; i++ {
208 | room := data.ChatRoom{
209 | Title: "test",
210 | Clients: []primitive.ObjectID{id, primitive.NewObjectID()},
211 | }
212 | room2 := data.ChatRoom{
213 | Title: "test",
214 | Clients: []primitive.ObjectID{primitive.NewObjectID(), primitive.NewObjectID()},
215 | }
216 | rooms = append(rooms, room)
217 | rooms = append(rooms, room2)
218 |
219 | }
220 | _, err = db.Rooms.InsertMany(context.TODO(), rooms)
221 | if err != nil {
222 | t.Fatalf("failure in adding rooms data to databse")
223 | }
224 | var result []data.ChatRoom
225 | result, err = db.GetHistoryOfUser(user)
226 | if err != nil || len(result) != 4 {
227 | t.Fatalf("failure in retriveing rooms data from databse")
228 | }
229 |
230 | }
231 |
232 | func TestGetUserData(t *testing.T) {
233 |
234 | db := ConnectDatabseTest()
235 | var users []interface{}
236 | ids := []primitive.ObjectID{}
237 | for i := 1; i < 10; i++ {
238 | user := data.UserData{
239 | Email: "test",
240 | ID: primitive.NewObjectID(),
241 | }
242 |
243 | users = append(users, user)
244 | ids = append(ids, user.ID)
245 |
246 | }
247 | _, err := db.Users.InsertMany(context.TODO(), users)
248 | if err != nil {
249 | t.Fatalf("failure in adding user data to databse")
250 | }
251 | var result []data.UserData
252 |
253 | result, err = db.GetUserData(ids)
254 | if err != nil || len(result) != 9 {
255 | t.Fatalf("failure in retriveing user data from databse")
256 | }
257 |
258 | }
259 |
--------------------------------------------------------------------------------
/controllers/api_test.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "bytes"
5 | "chicko_chat/database"
6 | "chicko_chat/models"
7 | "context"
8 | "encoding/json"
9 | "github.com/gin-gonic/gin"
10 | "github.com/stretchr/testify/assert"
11 | "go.mongodb.org/mongo-driver/bson/primitive"
12 | "net/http"
13 | "net/http/httptest"
14 | "testing"
15 | )
16 |
17 | func SetUpRouter() *gin.Engine {
18 | router := gin.Default()
19 | return router
20 | }
21 |
22 | func TestGetUserRoomsApi(t *testing.T) {
23 | db := database.ConnectDatabseTest()
24 | controller := Controller{
25 | DB: db,
26 | }
27 | r := SetUpRouter()
28 | r.POST("/user-rooms/", controller.GetUserRoomsApi)
29 |
30 | var rooms []interface{}
31 | id, err := primitive.ObjectIDFromHex("640778694829658eebc2d55b")
32 |
33 | user := &data.UserData{
34 | ID: id,
35 | Email: "testuser@mail.com",
36 | }
37 |
38 | for i := 1; i < 5; i++ {
39 | room := data.ChatRoom{
40 | Title: "test",
41 | Clients: []primitive.ObjectID{id, primitive.NewObjectID()},
42 | }
43 | rooms = append(rooms, room)
44 |
45 | }
46 | _, err = db.Rooms.InsertMany(context.TODO(), rooms)
47 | if err != nil {
48 | t.Fatalf("failure in adding rooms data to databse")
49 | }
50 |
51 | var result []data.ChatRoom
52 | result, err = db.GetHistoryOfUser(user)
53 | if err != nil || len(result) != 4 {
54 | t.Fatalf("failure in retriveing rooms data from databse")
55 | }
56 |
57 | jsonValue, _ := json.Marshal(user)
58 | req, _ := http.NewRequest("POST", "/user-rooms/", bytes.NewBuffer(jsonValue))
59 |
60 | w := httptest.NewRecorder()
61 | r.ServeHTTP(w, req)
62 |
63 | assert.Equal(t, http.StatusOK, w.Code)
64 | // Convert the JSON response to a map
65 | var response map[string][]data.ChatRoom
66 | err = json.Unmarshal([]byte(w.Body.String()), &response)
67 | // Grab the value & whether or not it exists
68 | value, exists := response["data"]
69 | // Make some assertions on the correctness of the response.
70 | assert.Nil(t, err)
71 | assert.True(t, exists)
72 | assert.Equal(t, value, result)
73 | }
74 |
75 | func TestCreateRoomApi(t *testing.T) {
76 | db := database.ConnectDatabseTest()
77 | controller := Controller{
78 | DB: db,
79 | }
80 | r := SetUpRouter()
81 | r.POST("/create-room/", controller.CreateRoomApi)
82 |
83 | id, err := primitive.ObjectIDFromHex("640778694829658eebc2d55b")
84 |
85 | room := &data.ChatRoom{
86 | Title: "test",
87 | Clients: []primitive.ObjectID{id},
88 | }
89 |
90 | jsonValue, _ := json.Marshal(room)
91 | req, _ := http.NewRequest("POST", "/create-room/", bytes.NewBuffer(jsonValue))
92 |
93 | w := httptest.NewRecorder()
94 | r.ServeHTTP(w, req)
95 |
96 | assert.Equal(t, http.StatusOK, w.Code)
97 | // Convert the JSON response to a map
98 | var response map[string]data.ChatRoom
99 | err = json.Unmarshal([]byte(w.Body.String()), &response)
100 | // Grab the value & whether or not it exists
101 | value, exists := response["data"]
102 | // Make some assertions on the correctness of the response.
103 | assert.Nil(t, err)
104 | assert.True(t, exists)
105 | assert.Equal(t, value.Clients, room.Clients)
106 | }
107 |
108 | func TestRoomHistoryApi(t *testing.T) {
109 | db := database.ConnectDatabseTest()
110 | controller := Controller{
111 | DB: db,
112 | }
113 | r := SetUpRouter()
114 | r.POST("/room-history/", controller.RoomHistoryApi)
115 |
116 | id, err := primitive.ObjectIDFromHex("640778694829658eebc2d55b")
117 |
118 | room := &data.ChatRoom{
119 | ID: id,
120 | Title: "test",
121 | Clients: []primitive.ObjectID{id},
122 | }
123 |
124 | var messages []interface{}
125 | for i := 1; i < 5; i++ {
126 | message := data.ChatEvent{
127 | EventType: data.Broadcast,
128 | ID: primitive.NewObjectID(),
129 | RoomID: id,
130 | Message: "test message",
131 | }
132 | messages = append(messages, message)
133 |
134 | }
135 | _, err = db.Messages.InsertMany(context.TODO(), messages)
136 | if err != nil {
137 | t.Fatalf("failure in adding messages data to databse")
138 | }
139 |
140 | jsonValue, _ := json.Marshal(room)
141 | req, _ := http.NewRequest("POST", "/room-history/", bytes.NewBuffer(jsonValue))
142 |
143 | w := httptest.NewRecorder()
144 | r.ServeHTTP(w, req)
145 |
146 | assert.Equal(t, http.StatusOK, w.Code)
147 | // Convert the JSON response to a map
148 | var response map[string][]data.ChatEvent
149 | err = json.Unmarshal([]byte(w.Body.String()), &response)
150 | // Grab the value & whether or not it exists
151 | value, exists := response["data"]
152 | // Make some assertions on the correctness of the response.
153 | assert.Nil(t, err)
154 | assert.True(t, exists)
155 | assert.Equal(t, len(value), len(messages))
156 | assert.Equal(t, value[0], messages[0])
157 |
158 | }
159 |
160 | func TestGetUserDetailsRoomApi(t *testing.T) {
161 | db := database.ConnectDatabseTest()
162 | controller := Controller{
163 | DB: db,
164 | }
165 | r := SetUpRouter()
166 | r.POST("/room-user-details/", controller.GetUserDetailsRoomApi)
167 |
168 | id, err := primitive.ObjectIDFromHex("640778694829658eebc2d55b")
169 | user_id, err := primitive.ObjectIDFromHex("640778694829658eebc2d55a")
170 | user_id2, err := primitive.ObjectIDFromHex("640778694829658eebc2d55c")
171 |
172 | user1 := &data.UserData{
173 | ID: user_id,
174 | Email: "test@gamil.com",
175 | }
176 |
177 | user2 := &data.UserData{
178 | ID: user_id2,
179 | Email: "test2@gamil.com",
180 | }
181 |
182 | room := &data.ChatRoom{
183 | ID: id,
184 | Title: "test",
185 | Clients: []primitive.ObjectID{user_id, user_id2},
186 | }
187 |
188 | var users []interface{}
189 |
190 | users = append(users, user1)
191 | users = append(users, user2)
192 |
193 | _, err = db.Users.InsertMany(context.TODO(), users)
194 | if err != nil {
195 | t.Fatalf("failure in adding user data to databse")
196 | }
197 |
198 | jsonValue, _ := json.Marshal(room)
199 | req, _ := http.NewRequest("POST", "/room-user-details/", bytes.NewBuffer(jsonValue))
200 |
201 | w := httptest.NewRecorder()
202 | r.ServeHTTP(w, req)
203 |
204 | assert.Equal(t, http.StatusOK, w.Code)
205 | // Convert the JSON response to a map
206 | var response map[string][]data.UserData
207 | err = json.Unmarshal([]byte(w.Body.String()), &response)
208 | // Grab the value & whether or not it exists
209 | value, exists := response["users"]
210 | // Make some assertions on the correctness of the response.
211 | assert.Nil(t, err)
212 | assert.True(t, exists)
213 |
214 | assert.Equal(t, value[0].ID, user1.ID)
215 | assert.Equal(t, value[1].ID, user2.ID)
216 | assert.Equal(t, value[0].Email, user1.Email)
217 | assert.Equal(t, value[1].Email, user2.Email)
218 |
219 | }
220 |
221 | func TestAddUserToRoomApi(t *testing.T) {
222 | db := database.ConnectDatabseTest()
223 | controller := Controller{
224 | DB: db,
225 | }
226 | r := SetUpRouter()
227 | r.POST("/add-user-room/", controller.AddUserToRoomApi)
228 |
229 | id, err := primitive.ObjectIDFromHex("640778694829658eebc2d55b")
230 | user_id, err := primitive.ObjectIDFromHex("640778694829658eebc2d55a")
231 | user_id2, err := primitive.ObjectIDFromHex("640778694829658eebc2d55c")
232 |
233 | room := &data.ChatRoom{
234 | ID: id,
235 | Title: "test",
236 | Clients: []primitive.ObjectID{user_id},
237 | }
238 |
239 | _, err = db.Rooms.InsertOne(context.TODO(), room)
240 |
241 | if err != nil {
242 | t.Fatalf("failure in adding room data to databse")
243 | }
244 | room.Clients[0] = user_id2
245 | jsonValue, _ := json.Marshal(room)
246 | req, _ := http.NewRequest("POST", "/add-user-room/", bytes.NewBuffer(jsonValue))
247 |
248 | w := httptest.NewRecorder()
249 | r.ServeHTTP(w, req)
250 |
251 | assert.Equal(t, http.StatusOK, w.Code)
252 | // Convert the JSON response to a map
253 | var response map[string]data.ChatRoom
254 | err = json.Unmarshal([]byte(w.Body.String()), &response)
255 | // Grab the value & whether or not it exists
256 | value, exists := response["data"]
257 | // Make some assertions on the correctness of the response.
258 | assert.Nil(t, err)
259 | assert.True(t, exists)
260 | assert.Equal(t, value.ID, room.ID)
261 | assert.Equal(t, len(value.Clients), 2)
262 | assert.Equal(t, value.Clients[0], user_id)
263 | assert.Equal(t, value.Clients[1], user_id2)
264 |
265 | }
266 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
2 | github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
3 | github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
4 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
5 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
6 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
7 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
11 | github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=
12 | github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs=
13 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
14 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
15 | github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
16 | github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
17 | github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
18 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
19 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
20 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
21 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
22 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
23 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
24 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
25 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
26 | github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
27 | github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
28 | github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
29 | github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
30 | github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
31 | github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
32 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
33 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
34 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
35 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
36 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
37 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
38 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
39 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
40 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
41 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
42 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
43 | github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
44 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
45 | github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
46 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
47 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
48 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
49 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
50 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
51 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
52 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
53 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
54 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
55 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
56 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
57 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
58 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
59 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
60 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
61 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
62 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
63 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
64 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
65 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
66 | github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
67 | github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
68 | github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
69 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
70 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
71 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
72 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
73 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
74 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
75 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
76 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
77 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
78 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
79 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
80 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
81 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
82 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
83 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
84 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
85 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
86 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
87 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
88 | github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
89 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
90 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
91 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
92 | github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
93 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
94 | github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
95 | github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
96 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
97 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
98 | github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=
99 | github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
100 | github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs=
101 | github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
102 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
103 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
104 | go.mongodb.org/mongo-driver v1.11.2 h1:+1v2rDQUWNcGW7/7E0Jvdz51V38XXxJfhzbV17aNHCw=
105 | go.mongodb.org/mongo-driver v1.11.2/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
106 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
107 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
108 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
109 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
110 | golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
111 | golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
112 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
113 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
114 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
115 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
116 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
117 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
118 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
119 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
120 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
121 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
122 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
123 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
124 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
125 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
126 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
127 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
128 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
129 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
130 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
131 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
132 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
133 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
134 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
135 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
136 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
137 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
138 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
139 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
140 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
141 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
142 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
143 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
144 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
145 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
146 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
147 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
148 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
149 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
150 |
--------------------------------------------------------------------------------