package main import ( "sort" "time" "verstak/internal/core/activity" "verstak/internal/core/nodes" syncsvc "verstak/internal/core/sync" "verstak/internal/i18n" ) type SystemViewDTO struct { ID string `json:"id"` Label string `json:"label"` } func (a *App) ListSystemViews() []SystemViewDTO { return []SystemViewDTO{ {ID: "today", Label: i18n.TF("ru", "nav.today")}, {ID: "inbox", Label: i18n.TF("ru", "nav.inbox")}, {ID: "activity", Label: i18n.TF("ru", "nav.activity")}, } } func (a *App) ListTodayView() (*TodayDashboardDTO, error) { aeByParent, err := a.activity.ListTodayEventsByParent() if err != nil { aeByParent = nil } todayNodes, _ := a.nodes.ListTodayNodes() type rawEvent struct { NodeID string EventType string TargetType string TargetID string TargetPath string Title string CreatedAt string } type caseInfo struct { Node nodes.Node Events []rawEvent } caseMap := make(map[string]*caseInfo) ensureCase := func(caseID string) *caseInfo { if ci, ok := caseMap[caseID]; ok { return ci } ci := &caseInfo{Events: nil} if n, err := a.nodes.GetActive(caseID); err == nil { ci.Node = *n } caseMap[caseID] = ci return ci } for pid, events := range aeByParent { ci := ensureCase(pid) for _, e := range events { ci.Events = append(ci.Events, rawEvent{ NodeID: e.NodeID, EventType: e.EventType, TargetType: e.TargetType, TargetID: e.TargetID, TargetPath: e.TargetPath, Title: e.Title, CreatedAt: e.CreatedAt, }) } } for _, n := range todayNodes { _ = ensureCase(n.ID) if ci := caseMap[n.ID]; ci.Node.ID == "" { ci.Node = n } } var groups []TodayGroupDTO var flatEvents []EventDTO summary := SummaryDTO{} for _, ci := range caseMap { if ci.Node.ID == "" { continue } summary.ChangedCases++ dtoEvents := make([]EventDTO, 0, len(ci.Events)) for _, re := range ci.Events { dtoEvents = append(dtoEvents, EventDTO{ ID: ci.Node.ID + "/" + re.NodeID + "/" + re.CreatedAt, NodeID: re.NodeID, EventType: re.EventType, TargetType: re.TargetType, TargetID: re.TargetID, TargetPath: re.TargetPath, Title: re.Title, CreatedAt: re.CreatedAt, }) switch re.EventType { case activity.TypeNoteCreated, activity.TypeNoteUpdated: summary.Notes++ case activity.TypeFileAdded, activity.TypeFileDeleted, activity.TypeFileRenamed, activity.TypeFileCopied, activity.TypeFileMoved: summary.Files++ } } last := ci.Node.UpdatedAt.Format(time.RFC3339) for _, e := range dtoEvents { if e.CreatedAt > last { last = e.CreatedAt } } groups = append(groups, TodayGroupDTO{ NodeID: ci.Node.ID, NodeTitle: ci.Node.Title, NodeKind: ci.Node.Type, Section: ci.Node.Section, LastActivityAt: last, Events: dtoEvents, }) flatEvents = append(flatEvents, dtoEvents...) } sort.Slice(groups, func(i, j int) bool { return groups[i].LastActivityAt > groups[j].LastActivityAt }) sort.Slice(flatEvents, func(i, j int) bool { return flatEvents[i].CreatedAt > flatEvents[j].CreatedAt }) return &TodayDashboardDTO{ Date: time.Now().Format("2006-01-02"), Summary: summary, Groups: groups, Events: flatEvents, }, nil } func (a *App) ListActivityFeed(limit, offset int) ([]EventDTO, error) { events, err := a.activity.ListRecent(limit, offset) if err != nil { return nil, err } result := make([]EventDTO, len(events)) for i, e := range events { result[i] = toEventDTO(e) } return result, nil } func (a *App) ListActivityByNode(nodeID string, limit, offset int) ([]EventDTO, error) { events, err := a.activity.ListByNode(nodeID, limit, offset) if err != nil { return nil, err } result := make([]EventDTO, len(events)) for i, e := range events { result[i] = toEventDTO(e) } return result, nil } func (a *App) CountActivityByNode(nodeID string) (int, error) { return a.activity.CountByNode(nodeID) } var _ = syncsvc.EntityNode