Compare commits

..

3 Commits

Author SHA1 Message Date
qzl 443c0c80ae fix: 修复后端代码违规并更新协议文档
- 修复 notifications 模块 datetime.now() 缺少时区问题
- 用 ApiProblemError 替换 BaseService 中的 HTTPException
- 更新协议文档:添加错误码、繁体字段、邀请相关协议
- 升级 Docker 镜像版本
2026-04-16 10:51:08 +08:00
qzl aea514a9b5 chore: record journal 2026-04-16 10:15:17 +08:00
qzl dcb955c6af chore(task): archive 04-15-divination-tutorial-first-visit 2026-04-16 10:15:07 +08:00
16 changed files with 151 additions and 22 deletions
@@ -3,14 +3,14 @@
"name": "divination-tutorial-first-visit",
"title": "divination-tutorial-first-visit",
"description": "",
"status": "planning",
"status": "completed",
"dev_type": null,
"scope": null,
"priority": "P2",
"creator": "zl-q",
"assignee": "zl-q",
"createdAt": "2026-04-15",
"completedAt": null,
"completedAt": "2026-04-16",
"branch": null,
"base_branch": "dev",
"worktree_path": null,
+4 -3
View File
@@ -8,8 +8,8 @@
<!-- @@@auto:current-status -->
- **Active File**: `journal-1.md`
- **Total Sessions**: 8
- **Last Active**: 2026-04-15
- **Total Sessions**: 9
- **Last Active**: 2026-04-16
<!-- @@@/auto:current-status -->
---
@@ -19,7 +19,7 @@
<!-- @@@auto:active-documents -->
| File | Lines | Status |
|------|-------|--------|
| `journal-1.md` | ~445 | Active |
| `journal-1.md` | ~477 | Active |
<!-- @@@/auto:active-documents -->
---
@@ -29,6 +29,7 @@
<!-- @@@auto:session-history -->
| # | Date | Title | Commits |
|---|------|-------|---------|
| 9 | 2026-04-16 | 起卦教程首次访问追踪 + Agent时间上下文 | `69b34bd` |
| 8 | 2026-04-15 | Session deletion anonymization for iOS compliance | `c2b726e` |
| 7 | 2026-04-15 | 六爻算法修复 + Prompt架构重构 + i18n输出规则 | `9598d16`, `be68681` |
| 6 | 2026-04-13 | 修复追问链路与上限判定 | - |
+32
View File
@@ -443,3 +443,35 @@ Replace soft-delete with anonymize + hard-delete. Add anonymous_session_snapshot
### Next Steps
- None - task complete
## Session 9: 起卦教程首次访问追踪 + Agent时间上下文
**Date**: 2026-04-16
**Task**: 起卦教程首次访问追踪 + Agent时间上下文
### Summary
实现了起卦教程首次访问追踪功能,包括后端 ProfileSettings 添加 DivinationTutorialSettings 字段、前端三处起卦页面添加首次访问检测和弹窗提示、使用本地状态管理避免并发覆盖问题、Agent系统提示添加时间上下文信息。
### Main Changes
### Git Commits
| Hash | Message |
|------|---------|
| `69b34bd` | (see git log) |
### Testing
- [OK] (Add test results)
### Status
[OK] **Completed**
### Next Steps
- None - task complete
+5 -3
View File
@@ -2,9 +2,8 @@ from __future__ import annotations
from uuid import UUID
from fastapi import HTTPException
from core.auth.models import CurrentUser
from core.http.errors import ApiProblemError, problem_payload
class BaseService:
@@ -15,7 +14,10 @@ class BaseService:
def require_current_user(self) -> CurrentUser:
if self._current_user is None:
raise HTTPException(status_code=401, detail="Unauthorized")
raise ApiProblemError(
status_code=401,
detail=problem_payload(code="AUTH_UNAUTHORIZED", detail="Unauthorized"),
)
return self._current_user
def require_user_id(self) -> UUID:
+2 -2
View File
@@ -1,6 +1,6 @@
from __future__ import annotations
from datetime import datetime
from datetime import datetime, timezone
from uuid import UUID
from sqlalchemy.dialects.postgresql import insert
@@ -85,7 +85,7 @@ class NotificationRepository:
if un is None:
return False
un.is_read = True
un.read_at = datetime.now()
un.read_at = datetime.now(timezone.utc)
await self._session.flush()
return True
+2 -2
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from datetime import datetime, timezone
from uuid import UUID
from core.http.errors import ApiProblemError, problem_payload
@@ -113,7 +113,7 @@ class NotificationService:
body=n.body,
payload=payload,
is_read=True,
read_at=un.read_at or datetime.now(),
read_at=un.read_at or datetime.now(timezone.utc),
created_at=un.created_at,
)
@@ -36,6 +36,11 @@ Gateway error codes from `backend/src/v1/auth/gateway.py`:
- `AUTH_VERIFICATION_CODE_INVALID`
- `AUTH_REFRESH_TOKEN_INVALID`
- `AUTH_REFRESH_TOKEN_MISSING`
- `AUTH_USER_NOT_FOUND`
Authorization error codes from `backend/src/v1/users/dependencies.py`:
- `AUTH_UNAUTHORIZED`
## Frontend route mapping
@@ -83,6 +83,12 @@ This document is the source of truth for backend RFC7807 `code` values consumed
|---|---:|---|---|
| `NOTIFICATION_NOT_FOUND` | 404 | Notification not found or not owned by current user | Show not-found message and refresh list |
## Invite
| code | status | meaning | frontend handling |
|---|---:|---|---|
| `INVITE_CODE_NOT_FOUND` | 404 | Invite code not found for current user | Show not-found message and trigger invite code bootstrap |
## Global
| code | status | meaning | frontend handling |
@@ -39,12 +39,15 @@ Protocol verification status:
### profiles
- PK: `id` (`auth.users.id`, `on delete cascade`)
- Core fields: `username`, `avatar_url`, `bio`, `settings`, `created_at`, `updated_at`, `deleted_at`
- Core fields: `username`, `avatar_url`, `bio`, `settings`, `referred_by`, `created_at`, `updated_at`, `deleted_at`
- Constraints:
- `username` not empty
- Indexes:
- `ix_profiles_username`
- `ix_profiles_settings_gin`
- Notes:
- `referred_by` is FK to `profiles.id` (`on delete set null`) for invite/referral tracking
- `settings` stores `ProfileSettingsV1` JSON including `preferences`, `privacy`, `notification`, `divination_tutorial`
### user_points
@@ -161,9 +161,11 @@ During run streaming, backend emits standard AG-UI lifecycle events and two divi
"binaryCode": "101001",
"changedBinaryCode": "100001",
"guaName": "山火贲",
"guaNameHant": "山火賁",
"upperName": "艮",
"lowerName": "离",
"targetGuaName": "山雷颐",
"targetGuaNameHant": "山雷頤",
"worldPosition": 1,
"responsePosition": 4,
"hasChangingYao": true,
@@ -192,7 +194,9 @@ During run streaming, backend emits standard AG-UI lifecycle events and two divi
{
"position": 1,
"spiritName": "虎",
"spiritNameHant": "虎",
"relationName": "官鬼",
"relationNameHant": "官鬼",
"tiganName": "卯",
"elementName": "木",
"isYang": true,
@@ -206,13 +210,27 @@ During run streaming, backend emits standard AG-UI lifecycle events and two divi
{
"position": 2,
"relationName": "父母",
"relationNameHant": "父母",
"tiganName": "午",
"elementName": "火"
}
]
],
"specialStatus": [],
"interactions": [],
"timeEffect": [],
"riChenZhangSheng": []
}
```
Field notes:
- `guaNameHant`, `targetGuaNameHant`: Traditional Chinese variants for卦名.
- `spiritNameHant`, `relationNameHant`: Traditional Chinese variants for六神/六亲 names.
- `specialStatus`: Special hexagram status indicators.
- `interactions`: 爻位 interaction descriptions.
- `timeEffect`: Time-based effect descriptions.
- `riChenZhangSheng`: 日辰长生相关 information.
### 2) `TEXT_MESSAGE_END`
- Standard final answer event.
+49
View File
@@ -0,0 +1,49 @@
# Invite Protocol (Frontend <-> Backend)
This document defines the invite code contract for authenticated users.
Protocol verification status:
- Backend route source: `backend/src/v1/invite/router.py`
- Backend service source: `backend/src/v1/invite/service.py`
- Backend schema source: `backend/src/v1/invite/schemas.py`
- Frontend mapping source: `apps/lib/features/settings/data/apis/invite_api.dart`
## Compatibility strategy
- Additive evolution only.
- Existing response fields are stable and must remain backward-compatible.
## Route
### GET /api/v1/invite/me
Get the current user's invite code information.
**Authorization**: Requires authenticated session. User identity from JWT `sub`.
**Response (200)**:
```json
{
"code": "ABC123XYZ",
"used_count": 5
}
```
Field rules:
- `code`: string, unique invite code assigned to the user
- `used_count`: integer `>= 0`, number of times this code has been used
## Error contract linkage
- RFC7807 + extension `code`, optional `params`.
- Shared registry: `docs/protocols/common/http-error-codes.md`.
- Error codes for this feature:
- `INVITE_CODE_NOT_FOUND` (404): Invite code not found for current user
## Data model linkage
- Invite codes are stored in `invite_codes` table.
- See `docs/protocols/common/user-points-chat-data-protocol.md` for `profiles.referred_by` field.
@@ -82,15 +82,15 @@ Field rules:
- `count`: integer `>= 0`
- Counts only notifications where `notifications.status = 'published'` and `notifications.deleted_at IS NULL`
### PATCH /api/v1/notifications/{id}/read
### PATCH /api/v1/notifications/{notification_id}/read
Mark a single notification as read. Idempotent.
**Authorization**: Requires authenticated session. `id` must belong to the current user's `user_notifications`.
**Authorization**: Requires authenticated session. `notification_id` must belong to the current user's `user_notifications`.
**Path parameters**:
- `id`: UUID of the `user_notifications` record
- `notification_id`: UUID of the `user_notifications` record
**Response (200)**:
@@ -109,6 +109,11 @@ Request:
"notification": {
"allow_notifications": true,
"allow_vibration": true
},
"divination_tutorial": {
"divination_entry_shown": false,
"auto_divination_shown": false,
"manual_divination_shown": false
}
}
}
@@ -118,6 +123,7 @@ Rules:
- `settings` must conform to `ProfileSettingsV1`.
- Additional fields are forbidden.
- `divination_tutorial` tracks user's tutorial completion state for divination flows.
Response:
+1 -1
View File
@@ -5,7 +5,7 @@ include:
services:
redis:
image: redis:7-alpine
image: redis:7.4.2-alpine
container_name: eryao-local-redis
restart: unless-stopped
ports:
+11 -4
View File
@@ -74,7 +74,7 @@ services:
rest:
container_name: supabase-rest
image: postgrest/postgrest:v14.6
image: postgrest/postgrest:v14.8
restart: unless-stopped
depends_on:
db:
@@ -92,7 +92,7 @@ services:
storage:
container_name: supabase-storage
image: supabase/storage-api:v1.44.2
image: supabase/storage-api:v1.48.26
restart: unless-stopped
depends_on:
db:
@@ -125,7 +125,7 @@ services:
meta:
container_name: supabase-meta
image: supabase/postgres-meta:v0.95.2
image: supabase/postgres-meta:v0.96.3
restart: unless-stopped
depends_on:
db:
@@ -146,12 +146,19 @@ services:
studio:
container_name: supabase-studio
image: supabase/studio:2026.03.16-sha-5528817
image: supabase/studio:2026.04.08-sha-205cbe7
restart: unless-stopped
depends_on:
meta:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "node -e \"require('http').get('http://0.0.0.0:3000/', (r) => process.exit(r.statusCode < 500 ? 0 : 1)).on('error', () => process.exit(1))\""]
interval: 10s
timeout: 10s
retries: 3
start_period: 15s
environment:
HOSTNAME: "0.0.0.0"
STUDIO_PG_META_URL: http://meta:8080
POSTGRES_PASSWORD: ${ERYAO_DATABASE__PASSWORD}
POSTGRES_HOST: db