simplify ListTodayView: remove fallback table queries, pure activity_events + ListTodayNodes

TodayView now uses only activity_events (source of truth) + ListTodayNodes (ensure changed cases appear). Removed direct queries to nodes (notes) and files tables — those will come from activity_events going forward.
This commit is contained in:
mirivlad 2026-06-01 02:45:55 +08:00
parent 08c9d5dbea
commit 5a1c4c6d7f
2 changed files with 12 additions and 123 deletions

Binary file not shown.

View File

@ -187,83 +187,20 @@ func (a *App) ListNodesBySection(section string) ([]NodeDTO, error) {
} }
// ListTodayView returns a dashboard of today's activity. // ListTodayView returns a dashboard of today's activity.
// For MVP this uses activity_events + root nodes changed today.
// Future: full Activity/Event Log system will be the single source of truth.
func (a *App) ListTodayView() (*TodayDashboardDTO, error) { func (a *App) ListTodayView() (*TodayDashboardDTO, error) {
start, end := activity.TodayBoundaries() // Collect events from activity_events, grouped by parent node.
// 1. Collect events from activity_events table.
aeByParent, err := a.activity.ListTodayEventsByParent() aeByParent, err := a.activity.ListTodayEventsByParent()
if err != nil { if err != nil {
aeByParent = nil aeByParent = nil
} }
// 2. Query notes created/updated today directly from nodes. // Root nodes that were created/updated today.
type noteRow struct {
ID string
ParentID string
Title string
CreatedAt string
}
var todayNotes []noteRow
if r, err := a.db.Query(`SELECT n.id, COALESCE(n.parent_id,''), n.title, n.created_at
FROM nodes n
WHERE n.deleted_at IS NULL AND n.type='note'
AND ((n.created_at >= ?1 AND n.created_at < ?2) OR (n.updated_at >= ?1 AND n.updated_at < ?2))`,
start, end); err == nil {
for r.Next() {
var nr noteRow
if err := r.Scan(&nr.ID, &nr.ParentID, &nr.Title, &nr.CreatedAt); err == nil {
todayNotes = append(todayNotes, nr)
}
}
r.Close()
}
// 3. Query files created today from files table.
type fileRow struct {
ID string
NodeID string
Filename string
ParentID string
CreatedAt string
}
var todayFiles []fileRow
if r, err := a.db.Query(`SELECT f.id, f.node_id, f.filename, COALESCE(n.parent_id,''), f.created_at
FROM files f
JOIN nodes n ON f.node_id = n.id
WHERE n.deleted_at IS NULL
AND (f.created_at >= ?1 AND f.created_at < ?2)`, start, end); err == nil {
for r.Next() {
var fr fileRow
if err := r.Scan(&fr.ID, &fr.NodeID, &fr.Filename, &fr.ParentID, &fr.CreatedAt); err == nil {
todayFiles = append(todayFiles, fr)
}
}
r.Close()
}
// Also include files updated today (but not created today).
if r, err := a.db.Query(`SELECT f.id, f.node_id, f.filename, COALESCE(n.parent_id,''), f.updated_at
FROM files f
JOIN nodes n ON f.node_id = n.id
WHERE n.deleted_at IS NULL
AND f.updated_at >= ?1 AND f.updated_at < ?2
AND f.created_at < ?1`, start, end); err == nil {
for r.Next() {
var fr fileRow
if err := r.Scan(&fr.ID, &fr.NodeID, &fr.Filename, &fr.ParentID, &fr.CreatedAt); err == nil {
todayFiles = append(todayFiles, fr)
}
}
r.Close()
}
// 4. Get root nodes that were created/updated today.
todayNodes, _ := a.nodes.ListTodayNodes() todayNodes, _ := a.nodes.ListTodayNodes()
// Build caseID → events map from all sources.
type rawEvent struct { type rawEvent struct {
NodeID string NodeID string
ParentID string
EventType string EventType string
Title string Title string
CreatedAt string CreatedAt string
@ -274,7 +211,6 @@ func (a *App) ListTodayView() (*TodayDashboardDTO, error) {
} }
caseMap := make(map[string]*caseInfo) caseMap := make(map[string]*caseInfo)
// Helper: ensure case entry exists.
ensureCase := func(caseID string) *caseInfo { ensureCase := func(caseID string) *caseInfo {
if ci, ok := caseMap[caseID]; ok { if ci, ok := caseMap[caseID]; ok {
return ci return ci
@ -293,7 +229,6 @@ func (a *App) ListTodayView() (*TodayDashboardDTO, error) {
for _, e := range events { for _, e := range events {
ci.Events = append(ci.Events, rawEvent{ ci.Events = append(ci.Events, rawEvent{
NodeID: e.NodeID, NodeID: e.NodeID,
ParentID: pid,
EventType: e.EventType, EventType: e.EventType,
Title: e.Title, Title: e.Title,
CreatedAt: e.CreatedAt, CreatedAt: e.CreatedAt,
@ -301,43 +236,7 @@ func (a *App) ListTodayView() (*TodayDashboardDTO, error) {
} }
} }
// Merge notes from direct query (avoid duplicates with ae). // Ensure all today's root nodes are present (even without events).
noteSeen := make(map[string]bool)
for _, nr := range todayNotes {
if noteSeen[nr.ID] {
continue
}
noteSeen[nr.ID] = true
caseID := nr.ParentID
ci := ensureCase(caseID)
ci.Events = append(ci.Events, rawEvent{
NodeID: nr.ID,
ParentID: caseID,
EventType: activity.TypeNoteCreated,
Title: nr.Title,
CreatedAt: nr.CreatedAt,
})
}
// Merge files.
fileSeen := make(map[string]bool)
for _, fr := range todayFiles {
if fileSeen[fr.ID] {
continue
}
fileSeen[fr.ID] = true
caseID := fr.ParentID
ci := ensureCase(caseID)
ci.Events = append(ci.Events, rawEvent{
NodeID: fr.NodeID,
ParentID: caseID,
EventType: activity.TypeFileAdded,
Title: fr.Filename,
CreatedAt: fr.CreatedAt,
})
}
// Merge today's root nodes (even without events).
for _, n := range todayNodes { for _, n := range todayNodes {
_ = ensureCase(n.ID) _ = ensureCase(n.ID)
if ci := caseMap[n.ID]; ci.Node.ID == "" { if ci := caseMap[n.ID]; ci.Node.ID == "" {
@ -345,7 +244,6 @@ func (a *App) ListTodayView() (*TodayDashboardDTO, error) {
} }
} }
// Build final groups and flat timeline.
var groups []TodayGroupDTO var groups []TodayGroupDTO
var flatEvents []EventDTO var flatEvents []EventDTO
summary := SummaryDTO{} summary := SummaryDTO{}
@ -358,17 +256,16 @@ func (a *App) ListTodayView() (*TodayDashboardDTO, error) {
dtoEvents := make([]EventDTO, 0, len(ci.Events)) dtoEvents := make([]EventDTO, 0, len(ci.Events))
for _, re := range ci.Events { for _, re := range ci.Events {
et := re.EventType
dtoEvents = append(dtoEvents, EventDTO{ dtoEvents = append(dtoEvents, EventDTO{
ID: ci.Node.ID + "/" + re.NodeID, ID: ci.Node.ID + "/" + re.NodeID,
NodeID: re.NodeID, NodeID: re.NodeID,
ParentID: re.ParentID, ParentID: ci.Node.ID,
EventType: et, EventType: re.EventType,
Title: re.Title, Title: re.Title,
Metadata: "{}", Metadata: "{}",
CreatedAt: re.CreatedAt, CreatedAt: re.CreatedAt,
}) })
switch et { switch re.EventType {
case activity.TypeNoteCreated, activity.TypeNoteUpdated: case activity.TypeNoteCreated, activity.TypeNoteUpdated:
summary.Notes++ summary.Notes++
case activity.TypeFileAdded, activity.TypeFileDeleted, activity.TypeFileRenamed, activity.TypeFileCopied, activity.TypeFileMoved: case activity.TypeFileAdded, activity.TypeFileDeleted, activity.TypeFileRenamed, activity.TypeFileCopied, activity.TypeFileMoved:
@ -377,13 +274,11 @@ func (a *App) ListTodayView() (*TodayDashboardDTO, error) {
} }
last := ci.Node.UpdatedAt.Format(time.RFC3339) last := ci.Node.UpdatedAt.Format(time.RFC3339)
if len(dtoEvents) > 0 {
for _, e := range dtoEvents { for _, e := range dtoEvents {
if e.CreatedAt > last { if e.CreatedAt > last {
last = e.CreatedAt last = e.CreatedAt
} }
} }
}
groups = append(groups, TodayGroupDTO{ groups = append(groups, TodayGroupDTO{
NodeID: ci.Node.ID, NodeID: ci.Node.ID,
@ -393,24 +288,18 @@ func (a *App) ListTodayView() (*TodayDashboardDTO, error) {
LastActivityAt: last, LastActivityAt: last,
Events: dtoEvents, Events: dtoEvents,
}) })
flatEvents = append(flatEvents, dtoEvents...) flatEvents = append(flatEvents, dtoEvents...)
} }
// Sort groups by lastActivityAt desc.
sort.Slice(groups, func(i, j int) bool { sort.Slice(groups, func(i, j int) bool {
return groups[i].LastActivityAt > groups[j].LastActivityAt return groups[i].LastActivityAt > groups[j].LastActivityAt
}) })
// Sort flat events by createdAt desc.
sort.Slice(flatEvents, func(i, j int) bool { sort.Slice(flatEvents, func(i, j int) bool {
return flatEvents[i].CreatedAt > flatEvents[j].CreatedAt return flatEvents[i].CreatedAt > flatEvents[j].CreatedAt
}) })
dateStr := time.Now().Format("2006-01-02")
return &TodayDashboardDTO{ return &TodayDashboardDTO{
Date: dateStr, Date: time.Now().Format("2006-01-02"),
Summary: summary, Summary: summary,
Groups: groups, Groups: groups,
Events: flatEvents, Events: flatEvents,