Skip to content

Commit

Permalink
refactor: replace pongo2 with templ
Browse files Browse the repository at this point in the history
  • Loading branch information
mgjules committed Sep 14, 2023
1 parent bd3505e commit 754a9a6
Show file tree
Hide file tree
Showing 13 changed files with 1,044 additions and 434 deletions.
147 changes: 0 additions & 147 deletions chat.go

This file was deleted.

167 changes: 167 additions & 0 deletions chat/chat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package chat

import (
"container/ring"
"errors"
"io"
"strings"
"sync"
"time"

goaway "github.com/TwiN/go-away"
"github.com/enescakir/emoji"
"github.com/mgjules/chat-demo/user"
"github.com/rs/xid"
"golang.org/x/exp/slog"
"golang.org/x/net/websocket"
)

const maxMessageSize = 256

// Message represents a single chat message.
type Message struct {
User *user.User
Content string
Time time.Time
}

// NewMessage creates a new Message.
func NewMessage(u *user.User, content string) (*Message, error) {
content = strings.TrimSpace(content)
if content == "" {
return nil, errors.New("message content cannot be empty")
}

rc := []rune(content)
if len(rc) > maxMessageSize {
content = string(rc[:maxMessageSize]) + "..."
}

content = goaway.Censor(emoji.Parse(content))

return &Message{
User: u,
Content: content,
Time: time.Now().UTC(),
}, nil
}

// Client represents the relationship between a user and websocket connections.
type Client struct {
user *user.User
conns map[*websocket.Conn]struct{}
}

// Room holds the state of a single chat room.
type Room struct {
mu sync.RWMutex
clients map[string]*Client
messages *ring.Ring
}

// NewRoom creates a new Room.
func NewRoom() *Room {
return &Room{
clients: make(map[string]*Client),
messages: ring.New(100),
}
}

// AddClient adds a websocket connection to a user as a client
// If the user does not already have a connection, thus no client
// it will be created and the method will return true.
func (r *Room) AddClient(u *user.User, ws *websocket.Conn) bool {
r.mu.Lock()
defer r.mu.Unlock()
id := u.ID.String()
var added bool
if _, found := r.clients[id]; !found {
r.clients[id] = &Client{
user: u,
conns: make(map[*websocket.Conn]struct{}),
}
added = true
}
r.clients[id].conns[ws] = struct{}{}

return added
}

// RemoveClient removes a websocket connection from a user.
// If the user does not have any websocket connection, its client will be removed
// and the method will return true.
func (r *Room) RemoveClient(id xid.ID, ws *websocket.Conn) bool {
r.mu.Lock()
defer r.mu.Unlock()
_, found := r.clients[id.String()]
if !found {
return false
}

delete(r.clients[id.String()].conns, ws)
if len(r.clients[id.String()].conns) == 0 {
delete(r.clients, id.String())
return true
}

return false
}

// NumUsers return the current number of users as clients.
func (r *Room) NumUsers() uint64 {
r.mu.RLock()
defer r.mu.RUnlock()

return uint64(len(r.clients))
}

// AddMessage adds a new chat message.
func (r *Room) AddMessage(m *Message) {
r.mu.Lock()
r.messages.Value = m
r.messages = r.messages.Next()
r.mu.Unlock()
}

// Messages returns the list of messages.
func (r *Room) Messages() []*Message {
r.mu.RLock()
defer r.mu.RUnlock()

messages := make([]*Message, 0)
r.messages.Do(func(m any) {
messages = append(messages, m.(*Message))
})

return messages
}

// Write implements the io.Writer interface.
func (r *Room) Write(p []byte) (int, error) {
r.mu.RLock()
defer r.mu.RUnlock()

writers := make([]io.Writer, 0)
for _, c := range r.clients {
for conn := range c.conns {
writers = append(writers, conn)
}
}

return io.MultiWriter(writers...).Write(p)
}

// IterateClients executes a function fn
// (e.g. a custom send mechanism or personalized messages per client) for all the clients.
func (r *Room) IterateClients(fn func(u *user.User, conn *websocket.Conn) error) {
r.mu.RLock()
defer r.mu.RUnlock()

for _, c := range r.clients {
for conn := range c.conns {
if err := fn(c.user, conn); err != nil {
slog.WarnContext(conn.Request().Context(), "send message", "err", "user.id", c.user.ID)
}
}
}
}
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ go 1.20

require (
github.com/TwiN/go-away v1.6.10
github.com/a-h/templ v0.2.334
github.com/enescakir/emoji v1.0.0
github.com/flosch/pongo2/v6 v6.0.0
github.com/go-chi/chi/v5 v5.0.10
github.com/go-chi/jwtauth/v5 v5.1.1
github.com/go-faker/faker/v4 v4.1.1
Expand All @@ -20,7 +20,6 @@ require (
require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.4 // indirect
Expand Down
14 changes: 3 additions & 11 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
github.com/TwiN/go-away v1.6.10 h1:ScxGvhyJPu7VqLJJCpVx9vXBlQXi4wme3Vwx4z1WeC4=
github.com/TwiN/go-away v1.6.10/go.mod h1:e0adzvKFM6LIbU+K8pczlqYMaoH/6OwdvQEqg9wSRSU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/a-h/templ v0.2.334 h1:/mKupkgHGeSSeC0KiGRvmUoRGQJuku9VGVhRP1CeWgY=
github.com/a-h/templ v0.2.334/go.mod h1:6Lfhsl3Z4/vXl7jjEjkJRCqoWDGjDnuKgzjYMDSddas=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand All @@ -9,8 +10,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etly
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/enescakir/emoji v1.0.0 h1:W+HsNql8swfCQFtioDGDHCHri8nudlK1n5p2rHCJoog=
github.com/enescakir/emoji v1.0.0/go.mod h1:Bt1EKuLnKDTYpLALApstIkAjdDrS/8IAgTkKp+WKFD0=
github.com/flosch/pongo2/v6 v6.0.0 h1:lsGru8IAzHgIAw6H2m4PCyleO58I40ow6apih0WprMU=
github.com/flosch/pongo2/v6 v6.0.0/go.mod h1:CuDpFm47R0uGGE7z13/tTlt1Y6zdxvr2RLT5LJhsHEU=
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/jwtauth/v5 v5.1.1 h1:Pjixqu5YkjE9sCLpzE01L0Q4sQzJIPdo7uz9r8ftp/c=
Expand All @@ -19,12 +18,9 @@ github.com/go-faker/faker/v4 v4.1.1 h1:zkxj/JH/aezB4R6cTEMKU7qcVScGhlB3qRtF3D7K+
github.com/go-faker/faker/v4 v4.1.1/go.mod h1:uuNc0PSRxF8nMgjGrrrU4Nw5cF30Jc6Kd0/FUTTYbhg=
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/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
Expand All @@ -38,11 +34,8 @@ github.com/lestrrat-go/jwx/v2 v2.0.12/go.mod h1:Mq4KN1mM7bp+5z/W5HS8aCNs5RKZ911G
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
Expand Down Expand Up @@ -105,7 +98,6 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading

0 comments on commit 754a9a6

Please sign in to comment.