From 6cc0bfd1d79074df790272b8091b1f0226d14283 Mon Sep 17 00:00:00 2001 From: zwlucas Date: Fri, 29 May 2026 15:57:37 -0300 Subject: feat: upload project Signed-off-by: zwlucas --- backend/internal/store/memory.go | 277 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 backend/internal/store/memory.go (limited to 'backend/internal/store/memory.go') 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 +} -- cgit v1.2.3