aboutsummaryrefslogtreecommitdiff
path: root/backend/internal/store/memory.go
diff options
context:
space:
mode:
Diffstat (limited to 'backend/internal/store/memory.go')
-rw-r--r--backend/internal/store/memory.go277
1 files changed, 277 insertions, 0 deletions
diff --git a/backend/internal/store/memory.go b/backend/internal/store/memory.go
new file mode 100644
index 0000000..a77e877
--- /dev/null
+++ b/backend/internal/store/memory.go
@@ -0,0 +1,277 @@
+package store
+
+import (
+ "errors"
+ "math/rand"
+ "sync"
+ "time"
+
+ "yaum/internal/model"
+)
+
+type MemoryStore struct {
+ mu sync.RWMutex
+ services map[int]*model.Service
+ heartbeats map[int][]*model.Heartbeat
+ maintenances []model.Maintenance
+ nextSvcID int
+ nextHbID int
+ nextMtnID int
+}
+
+func NewMemoryStore() *MemoryStore {
+ return &MemoryStore{
+ services: make(map[int]*model.Service),
+ heartbeats: make(map[int][]*model.Heartbeat),
+ maintenances: make([]model.Maintenance, 0),
+ nextSvcID: 1,
+ nextHbID: 1,
+ nextMtnID: 1,
+ }
+}
+
+func (s *MemoryStore) SeedMockData() {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ now := time.Now()
+ mockServices := []model.Service{
+ {Name: "Google", URL: "https://www.google.com", GroupName: "Geral", IntervalSeconds: 60, IsActive: true, CreatedAt: now},
+ {Name: "GitHub", URL: "https://www.github.com", GroupName: "Geral", IntervalSeconds: 60, IsActive: true, CreatedAt: now},
+ {Name: "JSONPlaceholder", URL: "https://jsonplaceholder.typicode.com/todos/1", GroupName: "APIs", IntervalSeconds: 60, IsActive: true, CreatedAt: now},
+ {Name: "API Local (falha esperada)", URL: "http://localhost:9999", GroupName: "APIs", IntervalSeconds: 60, IsActive: true, CreatedAt: now},
+ }
+
+ for _, svc := range mockServices {
+ svc.ID = s.nextSvcID
+ s.nextSvcID++
+ s.services[svc.ID] = &svc
+ s.heartbeats[svc.ID] = make([]*model.Heartbeat, 0)
+ s.seedHistory(svc.ID)
+ }
+}
+
+func (s *MemoryStore) seedHistory(serviceID int) {
+ now := time.Now()
+ for i := 29; i >= 0; i-- {
+ isUp := true
+ if rand.Intn(10) == 0 {
+ isUp = false
+ }
+
+ statusCode := 200
+ errMsg := ""
+ if !isUp {
+ statusCode = 0
+ errMsg = "connection timeout"
+ }
+
+ hb := &model.Heartbeat{
+ ID: s.nextHbID,
+ ServiceID: serviceID,
+ StatusCode: statusCode,
+ ResponseTimeMs: int64(50 + rand.Intn(450)),
+ IsUp: isUp,
+ ErrorMessage: errMsg,
+ TestedAt: now.Add(-time.Duration(i) * time.Minute),
+ }
+ s.nextHbID++
+ s.heartbeats[serviceID] = append(s.heartbeats[serviceID], hb)
+ }
+}
+
+func (s *MemoryStore) ListServices() ([]model.ServiceWithHeartbeat, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ result := make([]model.ServiceWithHeartbeat, 0, len(s.services))
+ for _, svc := range s.services {
+ swh := model.ServiceWithHeartbeat{Service: *svc}
+ if hbs := s.heartbeats[svc.ID]; len(hbs) > 0 {
+ swh.LastHeartbeat = hbs[len(hbs)-1]
+ }
+ result = append(result, swh)
+ }
+ return result, nil
+}
+
+func (s *MemoryStore) ListActiveServices() ([]model.Service, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ var result []model.Service
+ for _, svc := range s.services {
+ if svc.IsActive {
+ result = append(result, *svc)
+ }
+ }
+ return result, nil
+}
+
+func (s *MemoryStore) AddService(svc model.Service) (*model.Service, error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ svc.ID = s.nextSvcID
+ svc.IsActive = true
+ svc.CreatedAt = time.Now()
+ s.nextSvcID++
+ s.services[svc.ID] = &svc
+ s.heartbeats[svc.ID] = make([]*model.Heartbeat, 0)
+ return &svc, nil
+}
+
+func (s *MemoryStore) GetService(id int) (*model.Service, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ svc, ok := s.services[id]
+ if !ok {
+ return nil, nil
+ }
+ return svc, nil
+}
+
+func (s *MemoryStore) ToggleService(id int) (*model.Service, error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ svc, ok := s.services[id]
+ if !ok {
+ return nil, errors.New("servico nao encontrado")
+ }
+ svc.IsActive = !svc.IsActive
+ return svc, nil
+}
+
+func (s *MemoryStore) UpdateService(id int, svc model.Service) (*model.Service, error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ existing, ok := s.services[id]
+ if !ok {
+ return nil, errors.New("servico nao encontrado")
+ }
+
+ svc.ID = id
+ svc.IsActive = existing.IsActive
+ svc.CreatedAt = existing.CreatedAt
+ s.services[id] = &svc
+ return &svc, nil
+}
+
+func (s *MemoryStore) DeleteService(id int) error {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ if _, ok := s.services[id]; !ok {
+ return errors.New("servico nao encontrado")
+ }
+ delete(s.services, id)
+ delete(s.heartbeats, id)
+ return nil
+}
+
+func (s *MemoryStore) AddHeartbeat(hb model.Heartbeat) error {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ hb.ID = s.nextHbID
+ s.nextHbID++
+ s.heartbeats[hb.ServiceID] = append(s.heartbeats[hb.ServiceID], &hb)
+
+ maxHistory := 500
+ if len(s.heartbeats[hb.ServiceID]) > maxHistory {
+ s.heartbeats[hb.ServiceID] = s.heartbeats[hb.ServiceID][len(s.heartbeats[hb.ServiceID])-maxHistory:]
+ }
+ return nil
+}
+
+func (s *MemoryStore) GetHistory(serviceID int, limit int) ([]model.Heartbeat, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ hbs := s.heartbeats[serviceID]
+ if len(hbs) > limit {
+ hbs = hbs[len(hbs)-limit:]
+ }
+ result := make([]model.Heartbeat, len(hbs))
+ for i, hb := range hbs {
+ result[i] = *hb
+ }
+ return result, nil
+}
+
+func (s *MemoryStore) GetLastHeartbeat(serviceID int) (*model.Heartbeat, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ hbs := s.heartbeats[serviceID]
+ if len(hbs) == 0 {
+ return nil, nil
+ }
+ return hbs[len(hbs)-1], nil
+}
+
+func (s *MemoryStore) GetActiveMaintenance(now time.Time) (*model.Maintenance, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ for _, m := range s.maintenances {
+ if m.IsActive && now.After(m.StartTime) && now.Before(m.EndTime) {
+ return &m, nil
+ }
+ }
+ return nil, nil
+}
+
+func (s *MemoryStore) GetServiceStats(serviceID int) (*model.ServiceStats, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ hbs := s.heartbeats[serviceID]
+ now := time.Now()
+
+ calc := func(since time.Duration) (total, up int, avgMs float64) {
+ cutoff := now.Add(-since)
+ var sum int64
+ for _, h := range hbs {
+ if h.TestedAt.Before(cutoff) {
+ continue
+ }
+ total++
+ if h.IsUp {
+ up++
+ }
+ sum += h.ResponseTimeMs
+ }
+ if total > 0 {
+ avgMs = float64(sum) / float64(total)
+ }
+ return
+ }
+
+ t24, u24, a24 := calc(24 * time.Hour)
+ t7, u7, a7 := calc(7 * 24 * time.Hour)
+ t30, u30, a30 := calc(30 * 24 * time.Hour)
+
+ pct := func(up, total int) float64 {
+ if total == 0 {
+ return 0
+ }
+ return float64(up) / float64(total) * 100
+ }
+
+ return &model.ServiceStats{
+ ServiceID: serviceID,
+ Uptime24h: pct(u24, t24),
+ Uptime7d: pct(u7, t7),
+ Uptime30d: pct(u30, t30),
+ AvgResponseMs24h: a24,
+ AvgResponseMs7d: a7,
+ AvgResponseMs30d: a30,
+ TotalChecks24h: t24,
+ TotalChecks7d: t7,
+ TotalChecks30d: t30,
+ }, nil
+}