package activity import ( "time" "verstak/internal/core/storage" "verstak/internal/core/util" ) // Event types. const ( TypeNoteCreated = "note_created" TypeNoteUpdated = "note_updated" TypeFileAdded = "file_added" TypeFileDeleted = "file_deleted" TypeFileRenamed = "file_renamed" TypeFileCopied = "file_copied" TypeFileMoved = "file_moved" TypeFolderAdded = "folder_added" TypeFolderDeleted = "folder_deleted" TypeFolderRenamed = "folder_renamed" TypeNodeCreated = "node_created" TypeNodeUpdated = "node_updated" ) // Event represents an activity event. type Event struct { ID string `json:"id"` NodeID string `json:"node_id"` ParentID string `json:"parent_id,omitempty"` EventType string `json:"event_type"` Title string `json:"title"` Metadata string `json:"metadata"` CreatedAt string `json:"created_at"` } // Service records and queries activity events. type Service struct { db *storage.DB } func NewService(db *storage.DB) *Service { return &Service{db: db} } // Record inserts a new activity event. func (s *Service) Record(nodeID, parentID, eventType, title, metadata string) error { id := util.UUID7() now := time.Now().UTC().Format(time.RFC3339) if metadata == "" { metadata = "{}" } if parentID != "" { _, err := s.db.Exec( `INSERT INTO activity_events(id,node_id,parent_id,event_type,title,metadata,created_at) VALUES(?,?,?,?,?,?,?)`, id, nodeID, parentID, eventType, title, metadata, now) return err } _, err := s.db.Exec( `INSERT INTO activity_events(id,node_id,event_type,title,metadata,created_at) VALUES(?,?,?,?,?,?)`, id, nodeID, eventType, title, metadata, now) return err } // todayBoundaries returns the current local day in UTC. // TodayBoundaries returns the current local day in UTC as RFC3339 strings. func TodayBoundaries() (string, string) { now := time.Now() y, m, d := now.Date() start := time.Date(y, m, d, 0, 0, 0, 0, now.Location()) end := start.Add(24 * time.Hour) return start.UTC().Format(time.RFC3339), end.UTC().Format(time.RFC3339) } // ListTodayEvents returns all activity events from today. func (s *Service) ListTodayEvents() ([]Event, error) { start, end := TodayBoundaries() rows, err := s.db.Query( `SELECT id,node_id,COALESCE(parent_id,''),event_type,title,metadata,created_at FROM activity_events WHERE created_at >= ? AND created_at < ? ORDER BY created_at DESC`, start, end) if err != nil { return nil, err } defer rows.Close() var events []Event for rows.Next() { var e Event if err := rows.Scan(&e.ID, &e.NodeID, &e.ParentID, &e.EventType, &e.Title, &e.Metadata, &e.CreatedAt); err != nil { return nil, err } events = append(events, e) } return events, rows.Err() } // ListTodayEventsByParent returns activity events grouped by parent node. func (s *Service) ListTodayEventsByParent() (map[string][]Event, error) { events, err := s.ListTodayEvents() if err != nil { return nil, err } grouped := make(map[string][]Event, 8) for _, e := range events { pid := e.ParentID if pid == "" { pid = e.NodeID } grouped[pid] = append(grouped[pid], e) } return grouped, nil }