Skip to content

Commit 7a9054d

Browse files
naibanaiba/CloudCode
andcommitted
feat: redesign vault dashboard with 3-column layout, life metrics +1 pattern, mood sidebar (#63)
Implement all four self-journaling features from Monica v5: Backend: - Redesign Life Metrics to event-log pattern (each +1 click = one pivot row) - Add Increment and GetDetail APIs with weekly/monthly/yearly stats - Add vault-level dashboard life events API - Add DefaultActivityTab field to VaultResponse for tab persistence Frontend: - Rewrite VaultDetail.tsx with 3-column Monica-style layout - Left: Recent Contacts + Most Consulted - Center: Segmented tabs (Activity / Life Events / Life Metrics) - Right: Mood Summary + Upcoming Reminders + Due Tasks - Life Metrics tab: label + stats badges + "+1" button + monthly bar chart - Remove MoodTrackingModule from Contact detail (dashboard-only) - Remove standalone Feed/Life Metrics nav items E2E: - Rewrite life-metrics E2E for new +1/stats flow - Fix all 13 files using removed "View all contacts" dashboard link - Fix flaky admin serial tests (beforeEach lazy init) - Fix flaky DatePicker interaction (exclude today cell + in-view only) - Set playwright workers=1 to prevent shared-DB data pollution Closes #63 Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
1 parent 0804c98 commit 7a9054d

39 files changed

+1654
-524
lines changed

AGENTS.md

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ docker-compose.yml # 单容器部署
162162
- "Feed" (slug: `feed`) → feed
163163
- "Social" (slug: `social`) → relationships, pets, groups, addresses, contact_information
164164
- "Life & goals" (slug: `life-goals`) → life_events, goals
165+
- 注意:`mood_tracking` 模块已从 Contact Detail 移除,Mood Tracking 仅在 Vault Dashboard 右侧栏展示(只读摘要)
165166
- "Information" (slug: `information`) → documents, photos, notes, reminders, loans, tasks, calls, posts
166167

167168
**Vault 级种子**`seed_vault.go`)— 创建 Vault 时在事务内调用 `SeedVaultDefaults(tx, vaultID)`
@@ -170,6 +171,29 @@ docker-compose.yml # 单容器部署
170171
- LifeEventCategory(4 类 20 种事件类型)
171172
- VaultQuickFactsTemplate(2:Hobbies、Food preferences)
172173

174+
### Vault Dashboard 布局
175+
176+
仿照 Monica v5 的 3 列 + 3 Tab 布局:
177+
- **左栏**(240px):Recent Contacts + Most Consulted
178+
- **中栏**(fluid):Ant Design `Segmented` 切换 3 个 Tab
179+
- Activity(vault feed)
180+
- Your Life Events(`GET /api/vaults/:id/dashboard/lifeEvents`
181+
- Life Metrics(+1 increment pattern)
182+
- **右栏**(320px):Mood Summary(只读,来自 reports API) + Upcoming Reminders + Due Tasks
183+
- Tab 状态通过 `PUT /api/vaults/:id/defaultTab` 持久化到 `Vault.DefaultActivityTab` 字段
184+
- 响应式:≥1024px 三栏,768-1023px 两栏(隐藏左栏),<768px 单栏
185+
186+
### Life Metrics 架构(Issue #63 重构)
187+
188+
采用 Monica v5 的事件日志模式,非联系人关联模式:
189+
- `ContactLifeMetric` pivot 表每行 = 一次 "+1" 点击事件(含 `UserID``CreatedAt`
190+
- 统计(weekly/monthly/yearly events)通过 COUNT pivot 行按时间过滤计算
191+
- API:`POST /api/vaults/:id/lifeMetrics/:metricId/increment`(记录 +1)
192+
- API:`GET /api/vaults/:id/lifeMetrics/:metricId/detail`(月度柱状图数据)
193+
- `LifeMetric` model 不再有 `Contacts` many2many 关联
194+
- 前端 Life Metrics 独立页面(`/vaults/:id/life-metrics`)保留但已从导航移除
195+
- Feed 独立页面(`/vaults/:id/feed`)同上
196+
173197
### 个性化设置(Personalize)
174198

175199
- API 路径:`/api/settings/personalize/:entity`
@@ -371,7 +395,7 @@ React 19、TypeScript 严格模式、Vite 7、Ant Design v6、TanStack Query v5
371395
| API 路由(Swagger 统计) | 232 paths / 345 operations |
372396
| React 页面组件 | 62 |
373397
| 前端 API 客户端 | 60 |
374-
| i18n 翻译键 | ~1112(en + zh 各一份) |
398+
| i18n 翻译键 | ~1148(en + zh 各一份) |
375399

376400
### 测试数量明细
377401

@@ -387,8 +411,8 @@ React 19、TypeScript 严格模式、Vite 7、Ant Design v6、TanStack Query v5
387411
| Go Utils 测试 | 1 | 1 |
388412
| **Go 后端总计** | **106** | **~1014** |
389413
| React Vitest | 30 | 129 |
390-
| Playwright E2E | 25 | 181 |
391-
| **全部总计** | **161** | **1324+** |
414+
| Playwright E2E | 24 | 180 |
415+
| **全部总计** | **160** | **1323+** |
392416

393417
## 已知坑和注意事项
394418

@@ -447,7 +471,7 @@ defer cleanup()
447471
- 使用 `echo-swagger` **v1.4.1**(对应 Echo v4)。v1.5.0+ 依赖 Echo v5,不兼容。
448472
- **swag 类型解析陷阱**:handler 文件中的 `@Success ... dto.XxxResponse` 注解要求该文件能解析到 `dto` 包。如果 handler 的 Go 代码本身不 import `dto`(如 `currencies.go``vault_files.go`),swag 会报 `cannot find type definition`。解决方法:在文件中添加 `import "github.com/naiba/bonds/internal/dto"` + `var _ dto.XxxResponse`(类型锚点,防止 unused import 编译错误)。当前已有此模式的文件:`currencies.go``storage_info.go``user_management_extra.go``webauthn.go``avatar.go``calendar.go``companies.go``contact_photos.go``feed.go``post_photos.go``reports.go``vault_files.go``vault_tasks.go``vcard.go`
449473
- 全局注解(`@title``@BasePath``@securityDefinitions`)在 `cmd/server/main.go``func main()` 上方。
450-
- 当前统计:229 paths、342 operations、229 definitions
474+
- 当前统计:232 paths、345 operations。
451475

452476
### 前端 i18n 注意事项
453477

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ Monica is a beloved open-source personal CRM with 24k+ stars. But as a side proj
1919
- **Fast & lightweight** — Single binary, starts in milliseconds, minimal memory
2020
- **Easy to deploy** — One binary + SQLite. No PHP, no Composer, no Node runtime
2121
- **Modern UI** — React 19 + TypeScript, smooth SPA experience
22-
- **Well tested** — 1014 backend tests, 129 frontend tests, 174 E2E tests
22+
- **Well tested** — 1014 backend tests, 129 frontend tests, 180 E2E tests
2323
- **Community first** — Built for contributions and fast iteration
2424

2525
> **Credits**: Bonds stands on the shoulders of [@djaiss](https://github.com/djaiss), [@asbiin](https://github.com/asbiin), and the entire Monica community. The original Monica remains available under AGPL-3.0 at [monicahq/monica](https://github.com/monicahq/monica).
2626
2727
## Features
2828

2929
- **Contacts** — Full lifecycle management with notes, tasks, reminders, gifts, debts, activities, life events, pets, and more
30+
- **Vault Dashboard** — 3-column layout with activity feed, life events, life metrics tracking (+1 counter), mood summary, upcoming reminders, and due tasks
3031
- **Vaults** — Multi-vault data isolation with role-based access (Manager / Editor / Viewer)
3132
- **Reminders** — One-time and recurring (weekly/monthly/yearly), with email and Telegram notifications
3233
- **Full-text Search** — Bleve-powered CJK-aware search across contacts and notes
@@ -189,7 +190,7 @@ make setup # Install all dependencies
189190

190191
### API Documentation
191192

192-
Bonds provides auto-generated OpenAPI/Swagger documentation covering all 345 API endpoints.
193+
Bonds provides auto-generated OpenAPI/Swagger documentation covering all API endpoints.
193194

194195
To access the Swagger UI, either enable debug mode or toggle it on in Admin > Settings > Swagger:
195196
```bash

README_zh.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ Monica 是一个拥有 24k+ star 的优秀开源个人 CRM。但作为一个由
1919
- **快速轻量** — 单个二进制文件,毫秒级启动,内存占用极低
2020
- **部署简单** — 一个二进制 + SQLite 即可运行,无需 PHP/Composer/Node 运行时
2121
- **现代界面** — React 19 + TypeScript,流畅的 SPA 体验
22-
- **测试完善** — 1014 后端测试、1​29 前端测试、174 个 E2E 测试
22+
- **测试完善** — 1014 后端测试、129 前端测试、180 个 E2E 测试
2323
- **社区优先** — 为接受贡献和快速迭代而生
2424

2525
> **致谢**:Bonds 站在 [@djaiss](https://github.com/djaiss)[@asbiin](https://github.com/asbiin) 以及整个 Monica 社区的肩膀上。原版 Monica 仍以 AGPL-3.0 许可证在 [monicahq/monica](https://github.com/monicahq/monica) 持续提供。
2626
2727
## 功能特性
2828

2929
- **联系人管理** — 笔记、任务、提醒、礼物、债务、活动、人生事件、宠物等完整生命周期管理
30+
- **Vault 仪表盘** — 三栏布局:活动动态、生活事件、生活指标追踪(+1 计数)、心情摘要、即将到来的提醒和待办任务
3031
- **多 Vault** — 数据隔离 + 基于角色的权限控制(管理者 / 编辑者 / 查看者)
3132
- **提醒系统** — 一次性和周期性(每周/每月/每年),支持邮件和 Telegram 通知
3233
- **全文搜索** — 基于 Bleve 的中英文混合搜索,覆盖联系人和笔记
@@ -190,7 +191,7 @@ make setup # 安装所有依赖
190191

191192
### API 文档
192193

193-
Bonds 提供自动生成的 OpenAPI/Swagger 文档,覆盖全部 345 个 API 端点。
194+
Bonds 提供自动生成的 OpenAPI/Swagger 文档,覆盖全部 API 端点。
194195

195196
访问 Swagger UI,可通过调试模式或管理后台开关:
196197
```bash

server/internal/dto/life_metrics.go

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,40 @@ type UpdateLifeMetricRequest struct {
1010
Label string `json:"label" validate:"required" example:"Body Weight"`
1111
}
1212

13-
type AddLifeMetricContactRequest struct {
14-
ContactID string `json:"contact_id" validate:"required" example:"550e8400-e29b-41d4-a716-446655440000"`
13+
// LifeMetricStats holds event counts for a life metric scoped to the current user.
14+
type LifeMetricStats struct {
15+
WeeklyEvents int `json:"weekly_events" example:"3"`
16+
MonthlyEvents int `json:"monthly_events" example:"12"`
17+
YearlyEvents int `json:"yearly_events" example:"52"`
1518
}
1619

1720
type LifeMetricResponse struct {
18-
ID uint `json:"id" example:"1"`
19-
VaultID string `json:"vault_id" example:"550e8400-e29b-41d4-a716-446655440000"`
20-
Label string `json:"label" example:"Body Weight"`
21-
Contacts []LifeMetricContactBrief `json:"contacts,omitempty"`
22-
CreatedAt time.Time `json:"created_at" example:"2026-01-15T10:30:00Z"`
23-
UpdatedAt time.Time `json:"updated_at" example:"2026-01-15T10:30:00Z"`
21+
ID uint `json:"id" example:"1"`
22+
VaultID string `json:"vault_id" example:"550e8400-e29b-41d4-a716-446655440000"`
23+
Label string `json:"label" example:"Body Weight"`
24+
Stats LifeMetricStats `json:"stats"`
25+
CreatedAt time.Time `json:"created_at" example:"2026-01-15T10:30:00Z"`
26+
UpdatedAt time.Time `json:"updated_at" example:"2026-01-15T10:30:00Z"`
2427
}
2528

26-
type LifeMetricContactBrief struct {
27-
ID string `json:"id" example:"550e8400-e29b-41d4-a716-446655440000"`
28-
FirstName string `json:"first_name" example:"John"`
29-
LastName string `json:"last_name" example:"Doe"`
29+
// LifeMetricIncrementResponse wraps a full LifeMetricResponse with recalculated stats after an increment.
30+
type LifeMetricIncrementResponse = LifeMetricResponse
31+
32+
// LifeMetricMonthData holds event count for one calendar month.
33+
type LifeMetricMonthData struct {
34+
Month int `json:"month" example:"1"`
35+
FriendlyName string `json:"friendly_name" example:"January"`
36+
Events int `json:"events" example:"5"`
37+
}
38+
39+
// LifeMetricDetailResponse extends LifeMetricResponse with a 12-month breakdown for a given year.
40+
type LifeMetricDetailResponse struct {
41+
ID uint `json:"id" example:"1"`
42+
VaultID string `json:"vault_id" example:"550e8400-e29b-41d4-a716-446655440000"`
43+
Label string `json:"label" example:"Body Weight"`
44+
Stats LifeMetricStats `json:"stats"`
45+
Months []LifeMetricMonthData `json:"months"`
46+
MaxEvents int `json:"max_events" example:"10"`
47+
CreatedAt time.Time `json:"created_at" example:"2026-01-15T10:30:00Z"`
48+
UpdatedAt time.Time `json:"updated_at" example:"2026-01-15T10:30:00Z"`
3049
}

server/internal/dto/vault.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ type UpdateVaultRequest struct {
1313
}
1414

1515
type VaultResponse struct {
16-
ID string `json:"id" example:"550e8400-e29b-41d4-a716-446655440000"`
17-
AccountID string `json:"account_id" example:"550e8400-e29b-41d4-a716-446655440000"`
18-
Name string `json:"name" example:"Family"`
19-
Description string `json:"description" example:"Vault for family contacts"`
20-
CreatedAt time.Time `json:"created_at" example:"2026-01-15T10:30:00Z"`
21-
UpdatedAt time.Time `json:"updated_at" example:"2026-01-15T10:30:00Z"`
16+
ID string `json:"id" example:"550e8400-e29b-41d4-a716-446655440000"`
17+
AccountID string `json:"account_id" example:"550e8400-e29b-41d4-a716-446655440000"`
18+
Name string `json:"name" example:"Family"`
19+
Description string `json:"description" example:"Vault for family contacts"`
20+
DefaultActivityTab string `json:"default_activity_tab" example:"activity"`
21+
CreatedAt time.Time `json:"created_at" example:"2026-01-15T10:30:00Z"`
22+
UpdatedAt time.Time `json:"updated_at" example:"2026-01-15T10:30:00Z"`
2223
}

server/internal/handlers/life_events.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,32 @@ func NewLifeEventHandler(lifeEventService *services.LifeEventService) *LifeEvent
1818
return &LifeEventHandler{lifeEventService: lifeEventService}
1919
}
2020

21+
// ListVaultTimelineEvents godoc
22+
//
23+
// @Summary List vault-level timeline events
24+
// @Description Return paginated timeline events for the entire vault (dashboard view)
25+
// @Tags life-events
26+
// @Produce json
27+
// @Security BearerAuth
28+
// @Param vault_id path string true "Vault ID"
29+
// @Param page query integer false "Page number"
30+
// @Param per_page query integer false "Items per page"
31+
// @Success 200 {object} response.APIResponse{data=[]dto.TimelineEventResponse}
32+
// @Failure 401 {object} response.APIResponse
33+
// @Failure 500 {object} response.APIResponse
34+
// @Router /vaults/{vault_id}/dashboard/lifeEvents [get]
35+
func (h *LifeEventHandler) ListVaultTimelineEvents(c echo.Context) error {
36+
vaultID := c.Param("vault_id")
37+
page, _ := strconv.Atoi(c.QueryParam("page"))
38+
perPage, _ := strconv.Atoi(c.QueryParam("per_page"))
39+
40+
events, meta, err := h.lifeEventService.ListVaultTimelineEvents(vaultID, page, perPage)
41+
if err != nil {
42+
return response.InternalError(c, "err.failed_to_list_timeline_events")
43+
}
44+
return response.Paginated(c, events, meta)
45+
}
46+
2147
// ListTimelineEvents godoc
2248
//
2349
// @Summary List timeline events for a contact

server/internal/handlers/life_metrics.go

Lines changed: 40 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package handlers
33
import (
44
"errors"
55
"strconv"
6+
"time"
67

78
"github.com/labstack/echo/v4"
89
"github.com/naiba/bonds/internal/dto"
10+
"github.com/naiba/bonds/internal/middleware"
911
"github.com/naiba/bonds/internal/services"
1012
"github.com/naiba/bonds/pkg/response"
1113
)
@@ -21,7 +23,7 @@ func NewLifeMetricHandler(svc *services.LifeMetricService) *LifeMetricHandler {
2123
// List godoc
2224
//
2325
// @Summary List life metrics
24-
// @Description Return all life metrics for a vault
26+
// @Description Return all life metrics for a vault with stats for the current user
2527
// @Tags life-metrics
2628
// @Produce json
2729
// @Security BearerAuth
@@ -31,7 +33,8 @@ func NewLifeMetricHandler(svc *services.LifeMetricService) *LifeMetricHandler {
3133
// @Router /vaults/{vault_id}/lifeMetrics [get]
3234
func (h *LifeMetricHandler) List(c echo.Context) error {
3335
vaultID := c.Param("vault_id")
34-
metrics, err := h.svc.List(vaultID)
36+
userID := middleware.GetUserID(c)
37+
metrics, err := h.svc.List(vaultID, userID)
3538
if err != nil {
3639
return response.InternalError(c, "err.failed_to_list_life_metrics")
3740
}
@@ -136,80 +139,74 @@ func (h *LifeMetricHandler) Delete(c echo.Context) error {
136139
return response.NoContent(c)
137140
}
138141

139-
// AddContact godoc
142+
// Increment godoc
140143
//
141-
// @Summary Add contact to life metric
142-
// @Description Associate a contact with a life metric
144+
// @Summary Increment a life metric
145+
// @Description Record a "+1" event for the current user on a life metric
143146
// @Tags life-metrics
144-
// @Accept json
145147
// @Produce json
146148
// @Security BearerAuth
147-
// @Param vault_id path string true "Vault ID"
148-
// @Param id path integer true "Life Metric ID"
149-
// @Param request body dto.AddLifeMetricContactRequest true "Add contact request"
150-
// @Success 204 "No Content"
149+
// @Param vault_id path string true "Vault ID"
150+
// @Param id path integer true "Life Metric ID"
151+
// @Success 200 {object} response.APIResponse{data=dto.LifeMetricResponse}
151152
// @Failure 400 {object} response.APIResponse
152153
// @Failure 404 {object} response.APIResponse
153154
// @Failure 500 {object} response.APIResponse
154-
// @Router /vaults/{vault_id}/lifeMetrics/{id}/contacts [post]
155-
func (h *LifeMetricHandler) AddContact(c echo.Context) error {
155+
// @Router /vaults/{vault_id}/lifeMetrics/{id}/increment [post]
156+
func (h *LifeMetricHandler) Increment(c echo.Context) error {
156157
vaultID := c.Param("vault_id")
157158
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
158159
if err != nil {
159160
return response.BadRequest(c, "err.invalid_life_metric_id", nil)
160161
}
161-
var req dto.AddLifeMetricContactRequest
162-
if err := c.Bind(&req); err != nil {
163-
return response.BadRequest(c, "err.invalid_request_body", nil)
164-
}
165-
if err := validateRequest(req); err != nil {
166-
return response.ValidationError(c, map[string]string{"validation": err.Error()})
167-
}
168-
if err := h.svc.AddContact(uint(id), vaultID, req.ContactID); err != nil {
162+
userID := middleware.GetUserID(c)
163+
metric, err := h.svc.Increment(uint(id), vaultID, userID)
164+
if err != nil {
169165
if errors.Is(err, services.ErrLifeMetricNotFound) {
170166
return response.NotFound(c, "err.life_metric_not_found")
171167
}
172-
if errors.Is(err, services.ErrContactNotFound) {
173-
return response.NotFound(c, "err.contact_not_found")
174-
}
175-
return response.InternalError(c, "err.failed_to_add_life_metric_contact")
168+
return response.InternalError(c, "err.failed_to_increment_life_metric")
176169
}
177-
return response.NoContent(c)
170+
return response.OK(c, metric)
178171
}
179172

180-
// RemoveContact godoc
173+
// GetDetail godoc
181174
//
182-
// @Summary Remove contact from life metric
183-
// @Description Remove association between a contact and a life metric
175+
// @Summary Get life metric detail with monthly breakdown
176+
// @Description Return a life metric with 12-month event breakdown for a given year
184177
// @Tags life-metrics
185178
// @Produce json
186179
// @Security BearerAuth
187-
// @Param vault_id path string true "Vault ID"
188-
// @Param id path integer true "Life Metric ID"
189-
// @Param contact_id path string true "Contact ID"
190-
// @Success 204 "No Content"
180+
// @Param vault_id path string true "Vault ID"
181+
// @Param id path integer true "Life Metric ID"
182+
// @Param year query integer false "Year (defaults to current year)"
183+
// @Success 200 {object} response.APIResponse{data=dto.LifeMetricDetailResponse}
191184
// @Failure 400 {object} response.APIResponse
192185
// @Failure 404 {object} response.APIResponse
193186
// @Failure 500 {object} response.APIResponse
194-
// @Router /vaults/{vault_id}/lifeMetrics/{id}/contacts/{contact_id} [delete]
195-
func (h *LifeMetricHandler) RemoveContact(c echo.Context) error {
187+
// @Router /vaults/{vault_id}/lifeMetrics/{id}/detail [get]
188+
func (h *LifeMetricHandler) GetDetail(c echo.Context) error {
196189
vaultID := c.Param("vault_id")
197190
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
198191
if err != nil {
199192
return response.BadRequest(c, "err.invalid_life_metric_id", nil)
200193
}
201-
contactID := c.Param("contact_id")
202-
if contactID == "" {
203-
return response.BadRequest(c, "err.invalid_contact_id", nil)
194+
yearStr := c.QueryParam("year")
195+
year := time.Now().Year()
196+
if yearStr != "" {
197+
parsed, err := strconv.Atoi(yearStr)
198+
if err != nil {
199+
return response.BadRequest(c, "err.invalid_year", nil)
200+
}
201+
year = parsed
204202
}
205-
if err := h.svc.RemoveContact(uint(id), vaultID, contactID); err != nil {
203+
userID := middleware.GetUserID(c)
204+
detail, err := h.svc.GetDetail(uint(id), vaultID, userID, year)
205+
if err != nil {
206206
if errors.Is(err, services.ErrLifeMetricNotFound) {
207207
return response.NotFound(c, "err.life_metric_not_found")
208208
}
209-
if errors.Is(err, services.ErrContactNotFound) {
210-
return response.NotFound(c, "err.contact_not_found")
211-
}
212-
return response.InternalError(c, "err.failed_to_remove_life_metric_contact")
209+
return response.InternalError(c, "err.failed_to_get_life_metric_detail")
213210
}
214-
return response.NoContent(c)
211+
return response.OK(c, detail)
215212
}

server/internal/handlers/routes.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -553,9 +553,10 @@ func RegisterRoutes(e *echo.Echo, db *gorm.DB, cfg *config.Config, version strin
553553
vaultScoped.POST("/lifeMetrics", lifeMetricHandler.Create, requireEditor)
554554
vaultScoped.PUT("/lifeMetrics/:id", lifeMetricHandler.Update, requireEditor)
555555
vaultScoped.DELETE("/lifeMetrics/:id", lifeMetricHandler.Delete, requireEditor)
556-
vaultScoped.POST("/lifeMetrics/:id/contacts", lifeMetricHandler.AddContact, requireEditor)
557-
// BUG FIX (#56): Add missing DELETE route for removing contacts from life metrics
558-
vaultScoped.DELETE("/lifeMetrics/:id/contacts/:contact_id", lifeMetricHandler.RemoveContact, requireEditor)
556+
vaultScoped.POST("/lifeMetrics/:id/increment", lifeMetricHandler.Increment, requireEditor)
557+
vaultScoped.GET("/lifeMetrics/:id/detail", lifeMetricHandler.GetDetail)
558+
559+
vaultScoped.GET("/dashboard/lifeEvents", lifeEventHandler.ListVaultTimelineEvents)
559560

560561
vaultScoped.PUT("/defaultTab", vaultHandler.UpdateDefaultTab, requireEditor)
561562

0 commit comments

Comments
 (0)