From a1b599d03002fa925797c1d8e893ee591da2200f Mon Sep 17 00:00:00 2001 From: Cereska <8877199733@cms.k12.nc.us> Date: Thu, 26 Feb 2026 08:21:58 -0500 Subject: [PATCH] feat: first init --- go.mod | 8 ++++ go.sum | 4 ++ internal/cache/cache.go | 70 ++++++++++++++++++++++++++++++ internal/models/channel.go | 30 +++++++++++++ internal/models/user.go | 10 +++++ internal/repositories/messaging.go | 34 +++++++++++++++ internal/services/messaging.go | 28 ++++++++++++ internal/web/handlers.go | 1 + internal/web/middleware.go | 1 + internal/web/routes.go | 1 + main.go | 1 + 11 files changed, 188 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/cache/cache.go create mode 100644 internal/models/channel.go create mode 100644 internal/models/user.go create mode 100644 internal/repositories/messaging.go create mode 100644 internal/services/messaging.go create mode 100644 internal/web/handlers.go create mode 100644 internal/web/middleware.go create mode 100644 internal/web/routes.go create mode 100644 main.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..18ee462 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module chat + +go 1.25.0 + +require ( + github.com/labstack/echo/v5 v5.0.4 // indirect + golang.org/x/time v0.14.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..aab5e4e --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/labstack/echo/v5 v5.0.4 h1:ll3I/O8BifjMztj9dD1vx/peZQv8cR2CTUdQK6QxGGc= +github.com/labstack/echo/v5 v5.0.4/go.mod h1:SyvlSdObGjRXeQfCCXW/sybkZdOOQZBmpKF0bvALaeo= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= diff --git a/internal/cache/cache.go b/internal/cache/cache.go new file mode 100644 index 0000000..f79d814 --- /dev/null +++ b/internal/cache/cache.go @@ -0,0 +1,70 @@ +package cache + +import ( + "sync" + "time" +) + +// Item represents an item in the cache +type Item struct { + Value any + Expiration int64 +} + +// Cache +type Cache struct { + mu sync.RWMutex + items map[string]Item +} + +// Create new cache +func NewCache(cleanupInterval time.Duration) *Cache { + cache := &Cache{ + items: make(map[string]Item), + } + + // Start janitor process + go cache.janitor(cleanupInterval) + return cache +} + +// Set adds an item to the cache with a Time to Live (TTL) +func (c *Cache) Set(key string, value any, ttl time.Duration) { + expiration := time.Now().Add(ttl).UnixNano() + + // Lock mutex + c.mu.Lock() + defer c.mu.Unlock() + + c.items[key] = Item{ + Value: value, + Expiration: expiration, + } +} + +// Get retrieves an item, checking if it has expired +func (c *Cache) Get(key string) (any, bool) { + c.mu.RLock() + defer c.mu.RUnlock() + + item, found := c.items[key] + if !found || time.Now().UnixNano() > item.Expiration { + return nil, false + } + + return item.Value, true +} + +func (c *Cache) janitor(interval time.Duration) { + ticker := time.NewTicker(interval) + + for range ticker.C { + c.mu.Lock() + for k, v := range c.items { + if time.Now().UnixNano() > v.Expiration { + delete(c.items, k) + } + } + c.mu.Unlock() + } +} diff --git a/internal/models/channel.go b/internal/models/channel.go new file mode 100644 index 0000000..cc320bc --- /dev/null +++ b/internal/models/channel.go @@ -0,0 +1,30 @@ +package models + +import ( + "sync" + "time" +) + +type Channel struct { + ID string `json:"id"` + RoomName string `json:"room_name"` + TotalMessages int64 `json:"total_messages"` + Messages []Message + mu sync.RWMutex +} + +type Message struct { + Content string + Sender *User + Timestamp time.Time +} + +// Methods for channel +func (c *Channel) Send(msg Message) { + // Lock write + c.mu.Lock() + defer c.mu.Unlock() + + c.Messages = append(c.Messages, msg) + c.TotalMessages++ +} diff --git a/internal/models/user.go b/internal/models/user.go new file mode 100644 index 0000000..58fdd34 --- /dev/null +++ b/internal/models/user.go @@ -0,0 +1,10 @@ +package models + +type User struct { + ID string `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + SentMessages int64 `json:"sent_messages"` + IsBanned bool `json:"is_banned"` +} diff --git a/internal/repositories/messaging.go b/internal/repositories/messaging.go new file mode 100644 index 0000000..9d131aa --- /dev/null +++ b/internal/repositories/messaging.go @@ -0,0 +1,34 @@ +package repositories + +import ( + "chat/internal/models" + "fmt" + "time" +) + +func SendChatMessage(msg string, user *models.User, channel *models.Channel) error { + // Handle empty message + if len(msg) < 1 { + return fmt.Errorf("message cannot be empty") + } + + // Handle long message + if len(msg) > 500 { + return fmt.Errorf("message too long") + } + + // Check if user is banned + banned := user.IsBanned + if banned { + return fmt.Errorf("user: %s, is banned", user.ID) + } + + // Send message to channel + channel.Send(models.Message{ + Content: msg, + Sender: user, + Timestamp: time.Now(), + }) + + return nil +} diff --git a/internal/services/messaging.go b/internal/services/messaging.go new file mode 100644 index 0000000..974df2e --- /dev/null +++ b/internal/services/messaging.go @@ -0,0 +1,28 @@ +package services + +import ( + "chat/internal/cache" + "chat/internal/models" + repo "chat/internal/repositories" + "fmt" + "time" +) + +type MessagingService struct { + cache *cache.Cache +} + +// Send a chat message +func (s *MessagingService) Send(msg string, user *models.User, channel *models.Channel) error { + key := fmt.Sprintf("%s:messages:%s:%s", channel.ID, user.ID, time.Now()) + + if err := repo.SendChatMessage(msg, user, channel); err == nil { + // Store in cache + go func() { + s.cache.Set(key, msg, time.Minute*10) + }() + return nil + } else { + return err + } +} diff --git a/internal/web/handlers.go b/internal/web/handlers.go new file mode 100644 index 0000000..efb3895 --- /dev/null +++ b/internal/web/handlers.go @@ -0,0 +1 @@ +package web diff --git a/internal/web/middleware.go b/internal/web/middleware.go new file mode 100644 index 0000000..efb3895 --- /dev/null +++ b/internal/web/middleware.go @@ -0,0 +1 @@ +package web diff --git a/internal/web/routes.go b/internal/web/routes.go new file mode 100644 index 0000000..efb3895 --- /dev/null +++ b/internal/web/routes.go @@ -0,0 +1 @@ +package web diff --git a/main.go b/main.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/main.go @@ -0,0 +1 @@ +package main