feat: first init

This commit is contained in:
Cereska
2026-02-26 08:21:58 -05:00
commit a1b599d030
11 changed files with 188 additions and 0 deletions

8
go.mod Normal file
View File

@@ -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
)

4
go.sum Normal file
View File

@@ -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=

70
internal/cache/cache.go vendored Normal file
View File

@@ -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()
}
}

View File

@@ -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++
}

10
internal/models/user.go Normal file
View File

@@ -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"`
}

View File

@@ -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
}

View File

@@ -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
}
}

1
internal/web/handlers.go Normal file
View File

@@ -0,0 +1 @@
package web

View File

@@ -0,0 +1 @@
package web

1
internal/web/routes.go Normal file
View File

@@ -0,0 +1 @@
package web

1
main.go Normal file
View File

@@ -0,0 +1 @@
package main