detached 1cb11c4

feat(entities): add JSON tags and analytics types to domain entities

commit
1cb11c48331a20dc727219a634c9063d5e4f438c
author
brice · · 2025-12-16 19:44 UTC
committer
brice · · 2025-12-16 21:03 UTC
parent
5740371
signature
unsigned
2 files · +65 -27
M internal/domain/entity/favorite.go +20 -20
@@ -13,16 +13,16 @@ type TagID int64
1313
1414 // Favorite represents a bookmarked URL.
1515 type Favorite struct {
16- ID FavoriteID
17- URL string
18- Title string
19- FaviconURL string
20- FolderID *FolderID // nil = root level
21- ShortcutKey *int // 1-9 for quick access (Alt+1 through Alt+9)
22- Position int // Order within folder
23- Tags []Tag
24- CreatedAt time.Time
25- UpdatedAt time.Time
16+ ID FavoriteID `json:"id"`
17+ URL string `json:"url"`
18+ Title string `json:"title"`
19+ FaviconURL string `json:"favicon_url"`
20+ FolderID *FolderID `json:"folder_id"` // nil = root level
21+ ShortcutKey *int `json:"shortcut_key"` // 1-9 for quick access (Alt+1 through Alt+9)
22+ Position int `json:"position"` // Order within folder
23+ Tags []Tag `json:"tags,omitempty"`
24+ CreatedAt time.Time `json:"created_at"`
25+ UpdatedAt time.Time `json:"updated_at"`
2626 }
2727
2828 // NewFavorite creates a new favorite for a URL.
@@ -58,12 +58,12 @@ func (f *Favorite) HasTag(tagID TagID) bool {
5858
5959 // Folder represents a container for organizing favorites.
6060 type Folder struct {
61- ID FolderID
62- Name string
63- Icon string // Optional icon identifier
64- ParentID *FolderID // nil = root level
65- Position int // Order within parent
66- CreatedAt time.Time
61+ ID FolderID `json:"id"`
62+ Name string `json:"name"`
63+ Icon string `json:"icon"` // Optional icon identifier
64+ ParentID *FolderID `json:"parent_id"` // nil = root level
65+ Position int `json:"position"` // Order within parent
66+ CreatedAt time.Time `json:"created_at"`
6767 }
6868
6969 // NewFolder creates a new folder.
@@ -81,10 +81,10 @@ func (f *Folder) IsRoot() bool {
8181
8282 // Tag represents a label that can be applied to favorites.
8383 type Tag struct {
84- ID TagID
85- Name string
86- Color string // Hex color code (e.g., "#FF5733")
87- CreatedAt time.Time
84+ ID TagID `json:"id"`
85+ Name string `json:"name"`
86+ Color string `json:"color"` // Hex color code (e.g., "#FF5733")
87+ CreatedAt time.Time `json:"created_at"`
8888 }
8989
9090 // NewTag creates a new tag with default color.
@@ -13,16 +13,16 @@ type TagID int64
13 13
14 // Favorite represents a bookmarked URL.14 // Favorite represents a bookmarked URL.
15 type Favorite struct {15 type Favorite struct {
16- ID FavoriteID16+ ID FavoriteID `json:"id"`
17- URL string17+ URL string `json:"url"`
18- Title string18+ Title string `json:"title"`
19- FaviconURL string19+ FaviconURL string `json:"favicon_url"`
20- FolderID *FolderID // nil = root level20+ FolderID *FolderID `json:"folder_id"` // nil = root level
21- ShortcutKey *int // 1-9 for quick access (Alt+1 through Alt+9)21+ ShortcutKey *int `json:"shortcut_key"` // 1-9 for quick access (Alt+1 through Alt+9)
22- Position int // Order within folder22+ Position int `json:"position"` // Order within folder
23- Tags []Tag23+ Tags []Tag `json:"tags,omitempty"`
24- CreatedAt time.Time24+ CreatedAt time.Time `json:"created_at"`
25- UpdatedAt time.Time25+ UpdatedAt time.Time `json:"updated_at"`
26 }26 }
27 27
28 // NewFavorite creates a new favorite for a URL.28 // NewFavorite creates a new favorite for a URL.
@@ -58,12 +58,12 @@ func (f *Favorite) HasTag(tagID TagID) bool {
58 58
59 // Folder represents a container for organizing favorites.59 // Folder represents a container for organizing favorites.
60 type Folder struct {60 type Folder struct {
61- ID FolderID61+ ID FolderID `json:"id"`
62- Name string62+ Name string `json:"name"`
63- Icon string // Optional icon identifier63+ Icon string `json:"icon"` // Optional icon identifier
64- ParentID *FolderID // nil = root level64+ ParentID *FolderID `json:"parent_id"` // nil = root level
65- Position int // Order within parent65+ Position int `json:"position"` // Order within parent
66- CreatedAt time.Time66+ CreatedAt time.Time `json:"created_at"`
67 }67 }
68 68
69 // NewFolder creates a new folder.69 // NewFolder creates a new folder.
@@ -81,10 +81,10 @@ func (f *Folder) IsRoot() bool {
81 81
82 // Tag represents a label that can be applied to favorites.82 // Tag represents a label that can be applied to favorites.
83 type Tag struct {83 type Tag struct {
84- ID TagID84+ ID TagID `json:"id"`
85- Name string85+ Name string `json:"name"`
86- Color string // Hex color code (e.g., "#FF5733")86+ Color string `json:"color"` // Hex color code (e.g., "#FF5733")
87- CreatedAt time.Time87+ CreatedAt time.Time `json:"created_at"`
88 }88 }
89 89
90 // NewTag creates a new tag with default color.90 // NewTag creates a new tag with default color.
M internal/domain/entity/history.go +45 -7
@@ -4,13 +4,13 @@ import "time"
44
55 // HistoryEntry represents a visited URL in browsing history.
66 type HistoryEntry struct {
7- ID int64
8- URL string
9- Title string
10- FaviconURL string
11- VisitCount int64
12- LastVisited time.Time
13- CreatedAt time.Time
7+ ID int64 `json:"id"`
8+ URL string `json:"url"`
9+ Title string `json:"title"`
10+ FaviconURL string `json:"favicon_url"`
11+ VisitCount int64 `json:"visit_count"`
12+ LastVisited time.Time `json:"last_visited"`
13+ CreatedAt time.Time `json:"created_at"`
1414 }
1515
1616 // NewHistoryEntry creates a new history entry for a URL.
@@ -37,3 +37,41 @@ type HistoryMatch struct {
3737 Entry *HistoryEntry
3838 Score float64 // Match score (higher is better)
3939 }
40+
41+// HistoryStats contains aggregated history statistics.
42+type HistoryStats struct {
43+ TotalEntries int64 `json:"total_entries"`
44+ TotalVisits int64 `json:"total_visits"`
45+ UniqueDays int64 `json:"unique_days"`
46+}
47+
48+// DomainStat contains per-domain visit statistics.
49+type DomainStat struct {
50+ Domain string `json:"domain"`
51+ PageCount int64 `json:"page_count"`
52+ TotalVisits int64 `json:"total_visits"`
53+ LastVisit time.Time `json:"last_visit"`
54+}
55+
56+// HourlyDistribution contains visit counts by hour of day.
57+type HourlyDistribution struct {
58+ Hour int `json:"hour"`
59+ VisitCount int64 `json:"visit_count"`
60+}
61+
62+// DailyVisitCount contains visit counts by day.
63+type DailyVisitCount struct {
64+ Day string `json:"day"`
65+ Entries int64 `json:"entries"`
66+ Visits int64 `json:"visits"`
67+}
68+
69+// HistoryAnalytics contains all analytics data for the homepage.
70+type HistoryAnalytics struct {
71+ TotalEntries int64 `json:"total_entries"`
72+ TotalVisits int64 `json:"total_visits"`
73+ UniqueDays int64 `json:"unique_days"`
74+ TopDomains []*DomainStat `json:"top_domains"`
75+ DailyVisits []*DailyVisitCount `json:"daily_visits"`
76+ HourlyDistribution []*HourlyDistribution `json:"hourly_distribution"`
77+}
@@ -4,13 +4,13 @@ import "time"
4 4
5 // HistoryEntry represents a visited URL in browsing history.5 // HistoryEntry represents a visited URL in browsing history.
6 type HistoryEntry struct {6 type HistoryEntry struct {
7- ID int647+ ID int64 `json:"id"`
8- URL string8+ URL string `json:"url"`
9- Title string9+ Title string `json:"title"`
10- FaviconURL string10+ FaviconURL string `json:"favicon_url"`
11- VisitCount int6411+ VisitCount int64 `json:"visit_count"`
12- LastVisited time.Time12+ LastVisited time.Time `json:"last_visited"`
13- CreatedAt time.Time13+ CreatedAt time.Time `json:"created_at"`
14 }14 }
15 15
16 // NewHistoryEntry creates a new history entry for a URL.16 // NewHistoryEntry creates a new history entry for a URL.
@@ -37,3 +37,41 @@ type HistoryMatch struct {
37 Entry *HistoryEntry37 Entry *HistoryEntry
38 Score float64 // Match score (higher is better)38 Score float64 // Match score (higher is better)
39 }39 }
40+
41+// HistoryStats contains aggregated history statistics.
42+type HistoryStats struct {
43+ TotalEntries int64 `json:"total_entries"`
44+ TotalVisits int64 `json:"total_visits"`
45+ UniqueDays int64 `json:"unique_days"`
46+}
47+
48+// DomainStat contains per-domain visit statistics.
49+type DomainStat struct {
50+ Domain string `json:"domain"`
51+ PageCount int64 `json:"page_count"`
52+ TotalVisits int64 `json:"total_visits"`
53+ LastVisit time.Time `json:"last_visit"`
54+}
55+
56+// HourlyDistribution contains visit counts by hour of day.
57+type HourlyDistribution struct {
58+ Hour int `json:"hour"`
59+ VisitCount int64 `json:"visit_count"`
60+}
61+
62+// DailyVisitCount contains visit counts by day.
63+type DailyVisitCount struct {
64+ Day string `json:"day"`
65+ Entries int64 `json:"entries"`
66+ Visits int64 `json:"visits"`
67+}
68+
69+// HistoryAnalytics contains all analytics data for the homepage.
70+type HistoryAnalytics struct {
71+ TotalEntries int64 `json:"total_entries"`
72+ TotalVisits int64 `json:"total_visits"`
73+ UniqueDays int64 `json:"unique_days"`
74+ TopDomains []*DomainStat `json:"top_domains"`
75+ DailyVisits []*DailyVisitCount `json:"daily_visits"`
76+ HourlyDistribution []*HourlyDistribution `json:"hourly_distribution"`
77+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close