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 }