hailin
8d2fd3335a
feat(telemetry): add presence-service + Flutter telemetry module
...
## Backend — packages/services/presence-service (新微服务)
完整的 DDD + Clean Architecture 实现,移植自 RWADurian presence-service,
针对 IT0 架构做了以下适配:
### 核心功能
- 心跳接口: POST /api/v1/presence/heartbeat(JWT 验证,60s 间隔)
→ Redis Sorted Set `presence:online_users` 记录在线时间戳
→ 默认 5 分钟窗口判断在线(PRESENCE_WINDOW_SECONDS=300)
- 事件上报: POST /api/v1/analytics/events(批量,最多 50 条)
→ 写入 presence_event_log 表 + 更新 presence_device_profile
→ Redis HyperLogLog `presence:dau:{date}` 实时 DAU 估算
- 查询接口(需 AdminGuard):
- GET /api/v1/analytics/online-count — 实时在线人数
- GET /api/v1/analytics/online-history — 历史在线快照
- GET /api/v1/analytics/dau — DAU 统计
### IT0 适配要点
- JWT payload: `sub` = UUID userId(非 RWADurian 的 userSerialNum)
→ JwtAuthGuard: request.user = { userId: payload.sub, roles, tenantId }
- AdminGuard: 改为检查 `roles.includes('admin')`(非 type==='admin')
- 移除 Kafka EventPublisherService(IT0 无 Kafka)
- 移除 Prometheus MetricsService(IT0 无 Prometheus)
- 表前缀改为 `presence_`(避免与其他服务冲突)
- userId 字段 VarChar(36)(UUID 格式,非原来的 VarChar(20))
- Redis DB=10 隔离(独立 key 空间)
### 数据库表(public schema)
- presence_event_log — 事件流水(append-only)
- presence_device_profile — 设备快照(upsert,每台设备一行)
- presence_daily_active_users — DAU 日统计
- presence_online_snapshots — 在线人数每分钟快照
### 定时任务(@nestjs/schedule)
- 每分钟: 采集在线人数快照 → presence_online_snapshots
- 每天 01:05 (UTC+8): 计算前一天 DAU → presence_daily_active_users
---
## Flutter — it0_app/lib/core/telemetry (新模块)
### 文件结构
- telemetry_service.dart — 单例入口,统筹所有组件
- models/telemetry_event.dart — 事件模型,toServerJson() 将设备字段提升为顶层列
- models/device_context.dart — 设备上下文(Android/iOS 信息)
- models/telemetry_config.dart — 远程配置(采样率/开关,支持远端同步)
- collectors/device_info_collector.dart — 采集 device_info_plus 设备信息
- storage/telemetry_storage.dart — SharedPreferences 队列(最多 500 条)
- uploader/telemetry_uploader.dart — 批量上传到 /api/v1/analytics/events
- session/session_manager.dart — WidgetsBindingObserver 监听前后台切换
- session/session_events.dart — 会话事件常量
- presence/heartbeat_service.dart — 定时心跳 POST /api/v1/presence/heartbeat
- presence/presence_config.dart — 心跳配置(间隔/requiresAuth)
- telemetry.dart — barrel 导出
### 集成点
- app_router.dart _tryRestore(): TelemetryService().initialize() 在 auth 之前
- auth_provider.dart login/loginWithOtp: setUserId + setAccessToken + resumeAfterLogin
- auth_provider.dart tryRestoreSession: 恢复 userId + accessToken
- auth_provider.dart logout: pauseForLogout + clearUserId + clearAccessToken
### 新增依赖
- device_info_plus: ^10.1.0
- equatable: ^2.0.5
---
## 基础设施
### Dockerfile.service
- 在 builder 和 production 阶段均添加 presence-service/package.json 的 COPY
### docker-compose.yml
- 新增 presence-service 容器(端口 3011/13011)
- DATABASE_URL: postgresql://... (Prisma 所需连接串格式)
- REDIS_HOST/PORT/DB: 10(presence 独立 Redis DB)
- APP_PORT=3011, JWT_SECRET, PRESENCE_WINDOW_SECONDS=300
- api-gateway depends_on 新增 presence-service
### kong.yml (dbless 声明式)
- 新增 presence-service 服务(http://presence-service:3011)
- presence-routes: /api/v1/presence
- analytics-routes: /api/v1/analytics
- 对整个 presence-service 启用 JWT 插件(Kong 层鉴权)
### DB 迁移
- packages/shared/database/src/migrations/010-create-presence-tables.sql
— 4 张 presence_ 前缀表 + 完整索引(IF NOT EXISTS 幂等)
- run-migrations.ts: runSharedSchema() 中新增执行 010-create-presence-tables.sql
---
## 部署步骤(服务器)
1. git pull
2. 执行 presence 表迁移(首次):
docker exec it0-postgres psql -U it0 -d it0 \
-f /path/to/010-create-presence-tables.sql
或通过 migration runner:
cd /home/ceshi/it0 && node packages/shared/database/dist/run-migrations.js
3. 重建并启动 presence-service:
docker compose build presence-service api-gateway
docker compose up -d presence-service api-gateway
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 17:44:01 -08:00
hailin
e6f864d409
fix(version-service+gateway+app): fix APK download 404 and SHA-256 false failure
...
Three coordinated fixes to make in-app APK download work end-to-end:
1. version-service/main.ts: serve uploaded files as static assets via
NestExpressApplication.useStaticAssets('/data/versions', prefix:
'/downloads/versions'), so GET /downloads/versions/{platform}/{file}
returns the actual APK stored in the Docker volume.
2. kong.yml: add /downloads/versions route to Kong so requests from
the Flutter app can reach version-service through the API gateway.
Previously only /api/v1/versions and /api/app/version were routed;
the download URL returned by the check endpoint was unreachable (404).
3. download_manager.dart: skip SHA-256 verification when sha256Expected
is empty string. The check endpoint always returns sha256:"" because
version-service doesn't store file hashes. The previous code compared
actual_hash == "" which always failed, causing the downloaded file to
be deleted after a successful download.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 06:04:27 -08:00
hailin
369ecb2f29
feat(gateway): add Kong route for app version check endpoint
...
Add /api/app/version route to Kong declarative config so that the
Flutter app's GET /api/app/version/check?platform=¤t_version_code=
request can reach version-service through the API gateway.
Previously only /api/v1/versions was routed; the public check endpoint
served by AppVersionCheckController was unreachable (Kong returned
"no Route matched with those values").
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 05:33:56 -08:00
hailin
9ed80cd0bc
feat: implement complete commercial monetization loop (Phases 1-4)
...
## Phase 1 - Token Metering + Quota Enforcement
### Usage Tracking
- agent-service: add UsageRecord entity (per-tenant schema) tracking
inputTokens/outputTokens/costUsd per AI task
- Modify all 3 AI engines (claude-api, claude-code-cli, claude-agent-sdk)
to emit separate input/output token counts in the `completed` event
- claude-api-engine: costUsd = (input*3 + output*15) / 1,000,000
(claude-sonnet-4-5 pricing: $3/MTok in, $15/MTok out)
- agent.controller: persist UsageRecord and publish `usage.recorded`
event to Redis Streams on every task completion (non-blocking)
- shared/events: new events UsageRecordedEvent, SubscriptionChangedEvent,
QuotaExceededEvent, PaymentReceivedEvent
### Quota Enforcement
- TenantInfo: add maxServers, maxUsers, maxStandingOrders,
maxAgentTokensPerMonth fields
- TenantContextMiddleware: rewritten to query public.tenants table for
real quota values; 5-min in-memory cache; plan-based fallback on error
- TenantContextService: getTenant() returns null instead of throwing;
added getTenantOrThrow() for strict callers
- inventory-service/server.controller: 429 when maxServers exceeded
- ops-service/standing-order.controller: 429 when maxStandingOrders exceeded
- auth-service/auth.service: 429 when maxUsers exceeded
- 002-create-tenant-schema-template.sql: add usage_records table
## Phase 2 - billing-service (New Microservice, port 3010)
### Domain Layer (public schema, all UUIDs)
Entities: Plan, Subscription, Invoice, InvoiceItem, Payment, PaymentMethod,
UsageAggregate
Domain services:
- SubscriptionLifecycleService: full state machine (trialing -> active ->
past_due -> cancelled/expired); upgrades immediate, downgrades at period end
- InvoiceGeneratorService: monthly invoice = base fee + overage charges;
proration item for mid-cycle upgrades
- OverageCalculatorService: (totalTokens - includedTokens) * overageRate
### Infrastructure (all repos use DataSource directly, NOT TenantAwareRepository)
- PlanRepository, SubscriptionRepository, InvoiceRepository (atomic
transaction for invoice+items), PaymentRepository (payments + methods),
UsageAggregateRepository (UPSERT via ON CONFLICT for atomic accumulation)
### Application Use Cases
- CreateSubscriptionUseCase: called on tenant registration
- ChangePlanUseCase: upgrade (immediate + proration) or downgrade (scheduled)
- CancelSubscriptionUseCase: immediate or at-period-end
- GenerateMonthlyInvoiceUseCase: cron target (1st of month 00:05 UTC);
generates invoices, renews periods, applies scheduled downgrades
- AggregateUsageUseCase: Redis Streams consumer group billing-service,
upserts monthly usage aggregates from usage.recorded events
- CheckTokenQuotaUseCase: hard limit enforcement per plan
- CreatePaymentSessionUseCase + HandlePaymentWebhookUseCase
### REST API
- GET /api/v1/billing/plans
- GET/POST /api/v1/billing/subscription (+ /upgrade, /cancel)
- GET /api/v1/billing/invoices (paginated)
- GET /api/v1/billing/invoices/:id
- POST /api/v1/billing/invoices/:id/pay
- GET /api/v1/billing/usage/current + /history
- CRUD /api/v1/billing/payment-methods
- POST /api/v1/billing/webhooks/{stripe,alipay,wechat,crypto}
### Plan Seed (auto on startup via PlanSeedService)
- free: $0/mo, 100K tokens, no overage, hard limit 100%
- pro: $49.99/mo, 1M tokens, $8/MTok, hard limit 150%
- enterprise: $199.99/mo, 10M tokens, $5/MTok, no hard limit
## Phase 3 - Payment Provider Integration
### PaymentProviderRegistry (Strategy Pattern, mirrors EngineRegistry)
All providers use @Optional() injection; unconfigured providers omitted
- StripeProvider: PaymentIntent API; webhook via stripe.webhooks.constructEvent
- AlipayProvider: alipay-sdk; Native QR (precreate); RSA2 signature verify
- WeChatPayProvider: v3 REST; Native Pay code_url; AES-256-GCM decrypt;
HMAC-SHA256 request signing and webhook verification
- CryptoProvider: Coinbase Commerce; hosted checkout; HMAC-SHA256 verify
### WebhookController
All 4 webhook endpoints are public (no JWT) for payment provider callbacks.
rawBody: true enabled in main.ts for signature verification.
## Infrastructure Changes
- docker-compose.yml: billing-service container (port 13010);
added as dependency of api-gateway
- kong.yml: /api/v1/billing routes (JWT); /api/v1/billing/webhooks (public)
- 005-create-billing-tables.sql: 7 billing tables + invoice sequence +
ALTER tenants to add quota columns
- run-migrations.ts: 005 runs as part of shared schema step
## Phase 4 - Frontend
### Web Admin (Next.js)
New pages:
- /billing: subscription card + token usage bar + warning banner + invoices
- /billing/plans: comparison grid with USD/CNY toggle + upgrade/downgrade flow
- /billing/invoices: paginated table with Pay Now button
Sidebar: Billing group (CreditCard icon, 3 sub-items)
i18n: billing keys added to en + zh sidebar translations
### Flutter App
New feature module it0_app/lib/features/billing/:
- BillingOverviewPage: plan card + token LinearProgressIndicator +
latest invoice + upgrade button
- BillingProvider (FutureProvider): parallel fetch subscription/quota/invoice
Settings page: "订阅与用量" entry card
Router: /settings/billing sub-route
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 21:09:17 -08:00
hailin
f6dffe02c5
feat: add version-service for IT0 App version management
...
New NestJS microservice (port 3009) providing complete version management
API for IT0 App, designed to integrate with the existing mobile-upgrade
frontend (update.szaiai.com).
Backend — packages/services/version-service/ (9 new files):
- AppVersion entity: platform (ANDROID/IOS), versionName, buildNumber,
changelog, downloadUrl, fileSize, isForceUpdate, isEnabled, minOsVersion
- REST controller with 8 endpoints:
GET/POST /api/v1/versions — list (with platform/disabled filters) & create
GET/PUT/DELETE /api/v1/versions/:id — single CRUD
PATCH /api/v1/versions/:id/toggle — enable/disable
POST /api/v1/versions/upload — multipart APK/IPA upload (500MB limit)
POST /api/v1/versions/parse — extract version info from APK/IPA
- File storage: /data/versions/{platform}/ via Docker volume
- APK/IPA parsing: app-info-parser package
- Database: public.app_versions table (non-tenant, platform-level)
- No JWT auth (internal version management, consistent with existing apps)
Infrastructure changes:
- Dockerfile.service: added version-service package.json COPY lines
- docker-compose.yml: version-service container (13009:3009), version_data
volume, api-gateway depends_on
- kong.yml: version-service route (/api/v1/versions), CORS origin for
update.szaiai.com (mobile-upgrade frontend domain)
Deployment note: nginx needs /downloads/versions/ location + client_max_body_size 500m
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 07:48:31 -08:00
hailin
6e832c7615
feat: add voice I/O test page in Flutter settings
...
- TTS: text input → Kokoro synthesis → audio playback
- STT: long-press record → faster-whisper transcription
- Round-trip: record → STT → TTS → playback
- Added /api/v1/test route to Kong gateway for voice-service
- Accessible from Settings → 语音 I/O 测试
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 05:16:10 -08:00
hailin
6cd53e713c
fix: bypass JWT for voice WebSocket route (fixes 401 on WS upgrade)
...
根因:Kong 日志显示 voice WebSocket 连接被 JWT 插件返回 401,
因为 WebSocket RFC 6455 不支持自定义 header,Flutter 的
WebSocketChannel.connect 无法携带 Authorization header。
修复策略(业界标准做法):
1. Kong: 将 voice-service 的 JWT 从 service 级别改为 route
级别,仅在 voice-api 和 twilio-webhook 路由启用 JWT,
voice-ws 路由免除(session 创建已通过 JWT 验证,
session_id 本身作为认证凭据)
2. 后端: session_router 返回的 websocket_url 改为
/ws/voice/{session_id}(匹配 Kong voice-ws 路由路径)
3. FastAPI: 在 app 级别增加 /ws/voice/{session_id} 顶级
WebSocket 路由,委托给 session_router 的 handler
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 21:30:11 -08:00
hailin
5f827b0961
fix: revert ws/wss protocols - Kong OSS handles WS over http/https
...
Kong 3.7 OSS doesn't support ws/wss protocol identifiers (Enterprise only).
WebSocket upgrades are handled transparently over http/https protocols.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 17:02:53 -08:00
hailin
86d7cac631
fix: replace Socket.IO with raw WebSocket to fix 502 on /ws/agent
...
Socket.IO requires its own handshake protocol (EIO=4) which Kong cannot
proxy as a plain WebSocket upgrade, causing 502 Bad Gateway. Switch to
@nestjs/platform-ws (WsAdapter) with manual session room tracking so
Flutter's IOWebSocketChannel can connect directly.
Also add ws/wss protocols to Kong WebSocket routes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 16:52:43 -08:00
hailin
3816d6841d
fix: add users endpoint, admin route, and fix agent-config paths
...
- Add UsersController to auth-service for user CRUD (GET/POST/PUT/DELETE /api/v1/auth/users)
- Add Kong route /api/v1/admin -> auth-service for tenant management
- Remove AuthGuard from TenantController (Kong handles JWT)
- Fix frontend agent-config API paths from /api/v1/agent/config to /api/v1/agent-config
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 00:35:57 -08:00
hailin
f393a07092
fix: correct alert-rules API paths and remove audit ACL plugin
...
- Frontend alert-rules paths changed from /monitoring/alert-rules to
/monitor/alerts/rules to match backend routes
- Removed Kong ACL plugin on audit-routes (JWT auth is sufficient)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 00:21:50 -08:00
hailin
c710303b60
fix: per-service JWT in Kong, fix auth-service tenant-aware repos
...
- Replace global JWT plugin with per-service JWT (skip auth-service)
to fix auth routes being blocked by global JWT in DB-less mode
- Fix UserRepository and ApiKeyRepository to use standard TypeORM
instead of TenantAwareRepository (users are global, not per-schema)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 23:31:32 -08:00
hailin
48e47975ca
fix: configure Kong JWT auth flow with consumer credentials
...
- Add kid claim to auth-service JWT for Kong validation
- Add Kong consumer with JWT credential (shared secret via env)
- Add agent-config route to Kong for /api/v1/agent-config
- Kong Dockerfile uses entrypoint script to inject JWT_SECRET at runtime
- Fix frontend login path (/auth/login → /api/v1/auth/login)
- Extract tenantId from JWT on login and store as current_tenant
- Add auth guard in admin layout (redirect to /login if no token)
- Pass JWT_SECRET env var to Kong container in docker-compose
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 23:20:06 -08:00
hailin
e5dcfa6113
feat: configure it0.szaiai.com and it0api.szaiai.com domains
...
- Update Kong CORS origins to allow it0.szaiai.com
- Update WebSocket URL to wss://it0api.szaiai.com
- Fix proxy route to read API_BASE_URL at request time
(was being inlined at build time by Next.js standalone)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 22:54:17 -08:00
hailin
39718a9a09
fix: resolve runtime errors for NestJS, Kong, and voice-service
...
- Dockerfile.service: fix entry point path (dist/services/{name}/src/main)
due to tsconfig paths widening rootDir during compilation
- Kong config: remove unsupported ws/wss protocols (WebSocket works
automatically over http/https in Kong 3.7)
- voice-service: fix pipecat import path for v0.0.30 API
(pipecat.transports.network.websocket_server with lowercase class names)
- voice-service: add openai dependency required by pipecat anthropic service
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 19:00:03 -08:00
hailin
00f8801d51
Initial commit: IT0 AI-powered server cluster operations platform
...
Full-stack monorepo with DDD + Clean Architecture:
- Backend: 7 NestJS microservices + 5 shared libraries (TypeScript)
- Mobile: Flutter app with Riverpod (Dart)
- Web Admin: Next.js dashboard with Zustand + React Query
- Voice: Python voice service (STT/TTS/VAD)
- Infra: Docker Compose, K8s manifests, Turborepo build
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 22:54:37 -08:00