|
@@ -1,51 +1,214 @@
|
|
|
# Construction Timelapse — Session Memory
|
|
# Construction Timelapse — Session Memory
|
|
|
|
|
|
|
|
|
|
+> File này là **nhật ký phát triển** của dự án. Mỗi khi có thay đổi lớn, cập nhật phía dưới cùng (entry mới nhất).
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
## Tổng quan dự án
|
|
## Tổng quan dự án
|
|
|
-Hệ thống timelapse construction monitoring.
|
|
|
|
|
|
|
+
|
|
|
- **Repo Gogs**: `https://git.k9tech.space/kingkong/timelapse-2`
|
|
- **Repo Gogs**: `https://git.k9tech.space/kingkong/timelapse-2`
|
|
|
- **Token**: `a6224640fb6f576f6cba46f0e1646500c87226b6` (Personal Access Token)
|
|
- **Token**: `a6224640fb6f576f6cba46f0e1646500c87226b6` (Personal Access Token)
|
|
|
-- **Stack**: Next.js 14 + NestJS + PostgreSQL (Drizzle) + Redis + Python (device-agent)
|
|
|
|
|
-- **Package manager**: Bun (KHÔNG dùng npm vì workspace protocol không hoạt động với nexus registry)
|
|
|
|
|
|
|
+- **Stack**: Next.js 14 + NestJS + PostgreSQL (Drizzle) + Redis + Python
|
|
|
|
|
+- **Package manager**: Bun (KHÔNG dùng npm — workspace protocol không hoạt động với nexus registry)
|
|
|
- **Registry**: Luôn thêm `--registry https://registry.npmjs.org/` khi cài package
|
|
- **Registry**: Luôn thêm `--registry https://registry.npmjs.org/` khi cài package
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## Kiến trúc hiện tại
|
|
|
|
|
|
|
+## Kiến trúc hệ thống
|
|
|
|
|
|
|
|
```
|
|
```
|
|
|
-MacOS (code) → Gogs push → webhook → Pi 4 (deploy)
|
|
|
|
|
- ↓
|
|
|
|
|
- ┌─ postgres (docker)
|
|
|
|
|
- ├─ redis (docker)
|
|
|
|
|
- ├─ api-server (NestJS)
|
|
|
|
|
- ├─ web-dashboard (Next.js)
|
|
|
|
|
- └─ worker (Node)
|
|
|
|
|
|
|
+MacOS (code) → push Gogs → webhook → Pi 4 (deploy)
|
|
|
|
|
+ ↓
|
|
|
|
|
+ ┌─ postgres (docker, port 5432)
|
|
|
|
|
+ ├─ redis (docker, port 6379)
|
|
|
|
|
+ ├─ api-server (NestJS, port 3001)
|
|
|
|
|
+ ├─ web-dashboard (Next.js, port 3000)
|
|
|
|
|
+ ├─ worker (Node, stub)
|
|
|
|
|
+ └─ simulator (Python, tự động)
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
|
|
+**Simulator** = container giả lập device (claim → approve → heartbeat + upload ảnh).
|
|
|
|
|
+
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
## Workspace config
|
|
## Workspace config
|
|
|
-- **KHÔNG** dùng `apps/*` trong `workspaces` — `device-agent` là Python
|
|
|
|
|
-- Workspace config đúng:
|
|
|
|
|
- ```json
|
|
|
|
|
- "workspaces": ["apps/api-server", "apps/web-dashboard", "apps/worker", "packages/*"]
|
|
|
|
|
- ```
|
|
|
|
|
-- `@shared-types` **KHÔNG hợp lệ** — dùng `@shared/types`
|
|
|
|
|
-- Luôn `cd` vào workspace trước khi `bun add`
|
|
|
|
|
|
|
+
|
|
|
|
|
+```json
|
|
|
|
|
+"workspaces": ["apps/api-server", "apps/web-dashboard", "apps/worker", "packages/*"]
|
|
|
|
|
+```
|
|
|
|
|
+- `device-agent` và `simulator` là Python — KHÔNG đưa vào workspaces
|
|
|
|
|
+- Dùng `@shared/types`, không phải `@shared-types`
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Các lỗi đã gặp và cách fix
|
|
|
|
|
+
|
|
|
|
|
+| # | Lỗi | Fix |
|
|
|
|
|
+|---|------|-----|
|
|
|
|
|
+| 1 | `@shared-types` không hợp lệ | Đổi thành `@shared/types` |
|
|
|
|
|
+| 2 | Python packages trong npm workspaces | Loại khỏi workspaces |
|
|
|
|
|
+| 3 | `npm install` không hoạt động | `bun install --registry https://registry.npmjs.org/` |
|
|
|
|
|
+| 4 | `bun add` với nexus registry | Luôn `--registry https://registry.npmjs.org/` |
|
|
|
|
|
+| 5 | `db` không export được | Import từ `database.module.ts` |
|
|
|
|
|
+| 6 | `experimentalDecorators` thiếu | Thêm vào `packages/config/tsconfig.node.json` |
|
|
|
|
|
+| 7 | TSC output vào `src/` | Cleanup `*.js/*.d.ts/*.js.map` + fix outDir |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Database (Drizzle Schema)
|
|
|
|
|
+
|
|
|
|
|
+- **File**: `apps/api-server/src/db/schema.ts`
|
|
|
|
|
+- **16 tables**: organizations, projects, users, memberships, sessions, magic_links, devices, device_heartbeats, captures, videos, video_jobs, commands, alert_rules, alerts, audit_logs, activity_logs
|
|
|
|
|
+- **Enums**: org_status, project_status, device_status (có `pending`), capture_status, video_status, alert_severity, alert_type, alert_state, user_role, command_result_status
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Auth — Đã hoàn thành ✅
|
|
|
|
|
+
|
|
|
|
|
+| Feature | Chi tiết |
|
|
|
|
|
+|---------|---------|
|
|
|
|
|
+| Register/Login/Refresh/Logout | JWT + refresh token + bcrypt |
|
|
|
|
|
+| Magic links | Email-based passwordless |
|
|
|
|
|
+| Google OAuth | Stub (redirect + callback) |
|
|
|
|
|
+| JWT guard | `@UseGuards(JwtAuthGuard)` |
|
|
|
|
|
+| Dashboard auth flow | `storeTokens()`, `clearAuth()`, `apiFetch()` |
|
|
|
|
|
+| Protected routes | `pages/_app.tsx` redirect if no token |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## RBAC — Đã hoàn thành ✅
|
|
|
|
|
+
|
|
|
|
|
+| Role | Quyền |
|
|
|
|
|
+|------|--------|
|
|
|
|
|
+| `org_admin` | Full: org/project/member CRUD |
|
|
|
|
|
+| `project_manager` | Tạo/sửa project |
|
|
|
|
|
+| `viewer` | Read-only |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## API Endpoints (tổng hợp)
|
|
|
|
|
+
|
|
|
|
|
+### Auth (`/v1/auth/`)
|
|
|
|
|
+`POST register`, `POST login`, `POST refresh`, `POST logout`, `GET me`, `POST magic-link`
|
|
|
|
|
+
|
|
|
|
|
+### Orgs (`/v1/orgs/`)
|
|
|
|
|
+`POST`, `GET`, `GET /:id/members`, `POST /:id/members`, `PATCH /:id/members/:userId`, `DELETE /:id/members/:userId`
|
|
|
|
|
+
|
|
|
|
|
+### Projects (`/v1/projects/`)
|
|
|
|
|
+`POST`, `GET`, `GET /:id`, `PATCH /:id`, `DELETE /:id` — đều có JwtAuthGuard
|
|
|
|
|
+
|
|
|
|
|
+### Devices (`/v1/devices/`)
|
|
|
|
|
+| Endpoint | Auth | Mô tả |
|
|
|
|
|
+|----------|------|--------|
|
|
|
|
|
+| `POST claim` | none | Device xin claim code |
|
|
|
|
|
+| `GET claim/:code/status` | none | Poll trạng thái, nhận bootstrap API key |
|
|
|
|
|
+| `GET pending` | JWT (org_admin) | List pending devices |
|
|
|
|
|
+| `POST pending/:code/approve` | JWT (org_admin) | Approve + assign project |
|
|
|
|
|
+| `POST pending/:code/approve-internal` | X-Internal-Key | Simulator tự approve |
|
|
|
|
|
+| `POST pending/:code/reject` | JWT (org_admin) | Reject |
|
|
|
|
|
+| `POST :deviceId/heartbeat` | X-API-Key | Device heartbeat (bcrypt verify) |
|
|
|
|
|
+| `GET` | JWT | List all devices |
|
|
|
|
|
+| `GET stats` | JWT | Dashboard stats |
|
|
|
|
|
+| `GET :id` | JWT | Device detail |
|
|
|
|
|
+| `GET :id/heartbeats` | JWT | Heartbeat history |
|
|
|
|
|
+
|
|
|
|
|
+### Captures (`/v1/captures/`)
|
|
|
|
|
+| Endpoint | Auth | Mô tả |
|
|
|
|
|
+|----------|------|--------|
|
|
|
|
|
+| `POST upload` | X-API-Key | Upload ảnh (multipart), lưu local `/uploads/captures/` |
|
|
|
|
|
+| `POST register` | X-API-Key | Đăng ký metadata capture |
|
|
|
|
|
+| `GET project/:projectId` | JWT | List captures theo project |
|
|
|
|
|
+| `GET device/:deviceId` | JWT | List captures theo device |
|
|
|
|
|
+| `GET :id` | JWT | Chi tiết capture |
|
|
|
|
|
+
|
|
|
|
|
+### Realtime (Socket.io `/realtime`)
|
|
|
|
|
+Events: `device.heartbeat`, `device.status.changed`, `capture.uploaded`, `alert.opened`
|
|
|
|
|
+Dashboard subscribe theo project room: `project:X`
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Device Provisioning Flow
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+Device (Pi+DSLR) Server API Dashboard
|
|
|
|
|
+ │ │ │
|
|
|
|
|
+ │── POST /v1/devices/claim ──────────>│ │
|
|
|
|
|
+ |<─ { claimCode: "ABC123" } ──────────│ │
|
|
|
|
|
+ │ │── WS realtime ─────────────────>│ (new pending)
|
|
|
|
|
+ │ │ │
|
|
|
|
|
+ │ │<── POST /v1/devices/pending/:code/approve
|
|
|
|
|
+ │ │ (org_admin JWT) │
|
|
|
|
|
+ │── GET /v1/devices/claim/:code/status│ │
|
|
|
|
|
+ |<─ { status: approved, apiKey } ─────│ │
|
|
|
|
|
+ │ │ │
|
|
|
|
|
+ │── POST :id/heartbeat (X-API-Key) ──>│ │
|
|
|
|
|
+ │── POST /v1/captures/upload (file) ──>│ │
|
|
|
|
|
+ │ │── capture.uploaded WS ─────────>│
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Simulator Container (`apps/simulator`)
|
|
|
|
|
+
|
|
|
|
|
+**Mục đích**: Giả lập device kết nối thật vào server (POC mà không cần phần cứng thật).
|
|
|
|
|
+
|
|
|
|
|
+**Config**: env var trong `docker-compose.yml`
|
|
|
|
|
+```env
|
|
|
|
|
+SIM_SERVER_URL=http://api-server:3001
|
|
|
|
|
+SIM_DEVICE_COUNT=2 # số device giả lập
|
|
|
|
|
+SIM_CAPTURE_INTERVAL=30 # giây giữa mỗi capture
|
|
|
|
|
+SIM_INTERNAL_KEY=... # key để approve-internal endpoint
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**Luồng tự động**:
|
|
|
|
|
+1. Claim device → nhận claim code
|
|
|
|
|
+2. Auto-approve qua `/approve-internal` (X-Internal-Key auth)
|
|
|
|
|
+3. Poll `/claim/:code/status` → nhận bootstrap API key
|
|
|
|
|
+4. Heartbeat loop (30s)
|
|
|
|
|
+5. Capture loop (30s): Pillow tạo JPEG nhẹ (640×480, quality 60) → upload lên `/v1/captures/upload`
|
|
|
|
|
+
|
|
|
|
|
+**Files**:
|
|
|
|
|
+- `apps/simulator/simulator/main.py`
|
|
|
|
|
+- `apps/simulator/Dockerfile`
|
|
|
|
|
+- `apps/simulator/requirements.txt`
|
|
|
|
|
+
|
|
|
|
|
+**Lưu ý**: Cần ít nhất 1 project trong DB để simulator tự assign. Chạy seed trước.
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Device Agent (Pi thật — `apps/device-agent`)
|
|
|
|
|
+
|
|
|
|
|
+Chạy trên Pi 4 thật + DSLR:
|
|
|
|
|
+- `agent/main.py` — claim, poll, heartbeat, đọc thermal/disk metrics
|
|
|
|
|
+- `systemd/timelapse-agent.service` — systemd unit
|
|
|
|
|
+- `scripts/install-agent.sh` — Pi-only installer (kiểm tra EUID)
|
|
|
|
|
+- Config: `/etc/timelapse/agent.env`
|
|
|
|
|
+- Key lưu: `/boot/timelapse/agent.key` (chmod 600)
|
|
|
|
|
+- Metadata: `/boot/timelapse/agent.json`
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Mock Device CLI (`apps/device-agent/mock-device-cli.py`)
|
|
|
|
|
+
|
|
|
|
|
+Dùng cho test nhanh trên MacOS (không cần Docker):
|
|
|
|
|
+```bash
|
|
|
|
|
+# Claim device
|
|
|
|
|
+python3 apps/device-agent/mock-device-cli.py claim \
|
|
|
|
|
+ --server http://localhost:3001 --count 5
|
|
|
|
|
+
|
|
|
|
|
+# Heartbeat cho seed devices
|
|
|
|
|
+python3 apps/device-agent/mock-device-cli.py heartbeat \
|
|
|
|
|
+ --server http://localhost:3001 --demo --interval 5
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
## Gogs API
|
|
## Gogs API
|
|
|
|
|
+
|
|
|
```bash
|
|
```bash
|
|
|
# List repos
|
|
# List repos
|
|
|
curl -H "Authorization: token <TOKEN>" https://git.k9tech.space/api/v1/user/repos
|
|
curl -H "Authorization: token <TOKEN>" https://git.k9tech.space/api/v1/user/repos
|
|
|
|
|
|
|
|
-# Create repo
|
|
|
|
|
-curl -X POST -H "Authorization: token <TOKEN>" \
|
|
|
|
|
- -H "Content-Type: application/json" \
|
|
|
|
|
- -d '{"name":"repo-name","private":false}' \
|
|
|
|
|
- https://git.k9tech.space/api/v1/user/repos
|
|
|
|
|
-
|
|
|
|
|
# Create webhook
|
|
# Create webhook
|
|
|
curl -X POST -H "Authorization: token <TOKEN>" \
|
|
curl -X POST -H "Authorization: token <TOKEN>" \
|
|
|
-H "Content-Type: application/json" \
|
|
-H "Content-Type: application/json" \
|
|
@@ -59,118 +222,45 @@ git push -u origin main
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## Pi 4 Deploy Setup (TODO)
|
|
|
|
|
-Trên Pi 4 cần:
|
|
|
|
|
-1. Clone repo: `git clone https://git.k9tech.space/kingkong/timelapse-2.git /opt/timelapse`
|
|
|
|
|
-2. Tạo `/opt/timelapse/.env` với:
|
|
|
|
|
- ```
|
|
|
|
|
- GOGS_TOKEN=<deploy_token>
|
|
|
|
|
- GOGS_REPO_URL=https://git.k9tech.space/kingkong/timelapse-2.git
|
|
|
|
|
- DATABASE_URL=postgres://user:pass@postgres:5432/timelapse
|
|
|
|
|
- REDIS_URL=redis://redis:6379
|
|
|
|
|
- JWT_SECRET=...
|
|
|
|
|
- ```
|
|
|
|
|
-3. Chạy webhook server: `node scripts/webhook-server.js`
|
|
|
|
|
-4. Update Gogs webhook URL với IP thật của Pi 4
|
|
|
|
|
-5. Deploy script: `bash scripts/deploy.sh`
|
|
|
|
|
|
|
+## Docker Compose (Pi 4)
|
|
|
|
|
|
|
|
----
|
|
|
|
|
-
|
|
|
|
|
-## Docker Compose (in repo)
|
|
|
|
|
```bash
|
|
```bash
|
|
|
docker compose up -d postgres redis
|
|
docker compose up -d postgres redis
|
|
|
-docker compose up -d --build api-server web-dashboard worker
|
|
|
|
|
|
|
+docker compose up -d --build api-server web-dashboard worker simulator
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
----
|
|
|
|
|
-
|
|
|
|
|
-## Phase 1 — Trạng thái: ✅ HOÀN THÀNH
|
|
|
|
|
-
|
|
|
|
|
-### Backend (apps/api-server)
|
|
|
|
|
-- `src/modules/auth/` — AuthController/Service/Strategies stubs
|
|
|
|
|
-- `src/modules/devices/` — heartbeat endpoint + API key guard + repo/service/controller
|
|
|
|
|
-- `src/common/` — guards, decorators
|
|
|
|
|
-- `src/modules/orgs|projects|captures|videos|alerts/` — stubs
|
|
|
|
|
-- `src/realtime/` — stub
|
|
|
|
|
|
|
+**Volumes**:
|
|
|
|
|
+- `postgres_data` — PostgreSQL data
|
|
|
|
|
+- `redis_data` — Redis data
|
|
|
|
|
+- `timelapse_uploads` — Captured images (gắn vào api-server `/uploads/captures`)
|
|
|
|
|
|
|
|
-### Dashboard (apps/web-dashboard)
|
|
|
|
|
-- Dashboard home, device list, device detail pages
|
|
|
|
|
-- Tailwind CSS v3 + React Query
|
|
|
|
|
|
|
+**Env mới**:
|
|
|
|
|
+- `UPLOAD_DIR=/uploads/captures` (api-server)
|
|
|
|
|
+- `SIM_INTERNAL_KEY` (api-server + simulator)
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## Database (Drizzle Schema)
|
|
|
|
|
-- File: `apps/api-server/src/db/schema.ts`
|
|
|
|
|
-- Enum: org_status, project_status, device_status, capture_status, video_status, alert_severity, alert_type, user_role, command_result_status
|
|
|
|
|
-- Tables: organizations, projects, users, memberships, sessions, magic_links, devices, device_heartbeats, captures, videos, video_jobs, commands, alert_rules, alerts, audit_logs, activity_logs
|
|
|
|
|
|
|
+## Cài đặt trên Pi 4
|
|
|
|
|
|
|
|
----
|
|
|
|
|
|
|
+```bash
|
|
|
|
|
+cd /opt/timelapse
|
|
|
|
|
+git pull
|
|
|
|
|
|
|
|
-## Các lỗi đã gặp và cách fix
|
|
|
|
|
-1. `@shared-types` → `@shared/types`
|
|
|
|
|
-2. `device-agent` (Python) trong npm workspaces → loại khỏi workspaces
|
|
|
|
|
-3. `npm install` không hoạt động → `bun install --registry https://registry.npmjs.org/`
|
|
|
|
|
-4. `bun add` với nexus registry → luôn `--registry https://registry.npmjs.org/`
|
|
|
|
|
-5. `db` không export được → import từ `database.module.ts` riêng
|
|
|
|
|
-6. `experimentalDecorators` thiếu → thêm vào `packages/config/tsconfig.node.json`
|
|
|
|
|
-7. TSC output vào `src/` → cleanup *.js/*.d.ts/*.js.map + fix outDir
|
|
|
|
|
|
|
+# Rebuild
|
|
|
|
|
+docker compose up -d --build
|
|
|
|
|
|
|
|
----
|
|
|
|
|
|
|
+# Logs
|
|
|
|
|
+docker compose logs -f simulator
|
|
|
|
|
+docker compose logs -f api-server
|
|
|
|
|
|
|
|
-## Realtime WebSocket — ✅ ĐÃ LÀM
|
|
|
|
|
-- Socket.io gateway tại `src/realtime/gateways/realtime.gateway.ts`
|
|
|
|
|
-- Events: `device.heartbeat`, `device.status.changed`, `capture.uploaded`, `alert.opened`
|
|
|
|
|
-- Dashboard tự refetch khi nhận WS event (useSocket hook)
|
|
|
|
|
-- Client subscribe theo project room: `subscribe:project`
|
|
|
|
|
-
|
|
|
|
|
-## Device installer (Pi) — ✅ ĐÃ LÀM
|
|
|
|
|
-- `apps/device-agent/agent/main.py`
|
|
|
|
|
- - claim device, poll approval, save key, heartbeat loop
|
|
|
|
|
-- `apps/device-agent/systemd/timelapse-agent.service`
|
|
|
|
|
-- `apps/device-agent/scripts/install-agent.sh`
|
|
|
|
|
-- `apps/device-agent/requirements.txt`
|
|
|
|
|
-- `apps/device-agent/README.md`
|
|
|
|
|
-
|
|
|
|
|
-## Device installer (Pi) — ✅ ĐÃ LÀM
|
|
|
|
|
-- `apps/device-agent/agent/main.py`
|
|
|
|
|
- - claim device, poll approval, save key, heartbeat loop
|
|
|
|
|
-- `apps/device-agent/systemd/timelapse-agent.service`
|
|
|
|
|
-- `apps/device-agent/scripts/install-agent.sh`
|
|
|
|
|
-- `apps/device-agent/requirements.txt`
|
|
|
|
|
-- `apps/device-agent/README.md`
|
|
|
|
|
-
|
|
|
|
|
-## TODO — Phase tiếp theo
|
|
|
|
|
-1. **DB migration**: drizzle-kit push + seed data (trên Pi)
|
|
|
|
|
-2. **Role-based access control nâng cao** (org_admin/project_manager/viewer)
|
|
|
|
|
-3. **Device-agent** (Python) heartbeat sender
|
|
|
|
|
-4. **Realtime WebSocket** cho status update
|
|
|
|
|
-5. **Google OAuth thật** (redirect + callback)
|
|
|
|
|
-6. **Worker thực tế** (hiện vẫn restart vì stub)
|
|
|
|
|
-
|
|
|
|
|
-## Đã hoàn thành gần đây
|
|
|
|
|
-- Auth thực sự: register/login/refresh/logout + JWT guard + /auth/me
|
|
|
|
|
-- Dashboard auth flow: /login, ProtectedRoute, logout, auto attach Bearer token
|
|
|
|
|
-- Orgs CRUD API (`/v1/orgs`) có JWT guard
|
|
|
|
|
-- RBAC theo role:
|
|
|
|
|
- - `org_admin`: full org/project/member management
|
|
|
|
|
- - `project_manager`: create/update project
|
|
|
|
|
- - `viewer`: read-only
|
|
|
|
|
-- Membership management endpoints:
|
|
|
|
|
- - `GET /v1/orgs/:id/members`
|
|
|
|
|
- - `POST /v1/orgs/:id/members` (org_admin)
|
|
|
|
|
- - `PATCH /v1/orgs/:id/members/:userId` (org_admin)
|
|
|
|
|
- - `DELETE /v1/orgs/:id/members/:userId` (org_admin)
|
|
|
|
|
-- Projects CRUD API:
|
|
|
|
|
- - `POST /v1/projects`
|
|
|
|
|
- - `GET /v1/projects?orgId=`
|
|
|
|
|
- - `GET /v1/projects/:id`
|
|
|
|
|
- - `PATCH /v1/projects/:id`
|
|
|
|
|
- - `DELETE /v1/projects/:id`
|
|
|
|
|
- (đều có JwtAuthGuard)
|
|
|
|
|
|
|
+# DB migration (cần chạy 1 lần sau khi schema thay đổi)
|
|
|
|
|
+docker compose exec api-server bun run migrate
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
## Lệnh nhanh (MacOS dev)
|
|
## Lệnh nhanh (MacOS dev)
|
|
|
|
|
+
|
|
|
```bash
|
|
```bash
|
|
|
# Cài deps
|
|
# Cài deps
|
|
|
bun install --registry https://registry.npmjs.org/
|
|
bun install --registry https://registry.npmjs.org/
|
|
@@ -179,7 +269,7 @@ bun install --registry https://registry.npmjs.org/
|
|
|
cd apps/api-server && bun run dev
|
|
cd apps/api-server && bun run dev
|
|
|
cd apps/web-dashboard && bun run dev
|
|
cd apps/web-dashboard && bun run dev
|
|
|
|
|
|
|
|
-# Build (verify)
|
|
|
|
|
|
|
+# Build verify
|
|
|
cd apps/api-server && bun run build
|
|
cd apps/api-server && bun run build
|
|
|
cd apps/web-dashboard && bun run build
|
|
cd apps/web-dashboard && bun run build
|
|
|
|
|
|
|
@@ -187,5 +277,62 @@ cd apps/web-dashboard && bun run build
|
|
|
curl -X POST http://localhost:3001/v1/devices/test-device/heartbeat \
|
|
curl -X POST http://localhost:3001/v1/devices/test-device/heartbeat \
|
|
|
-H "Content-Type: application/json" \
|
|
-H "Content-Type: application/json" \
|
|
|
-H "X-API-Key: dev-key-123" \
|
|
-H "X-API-Key: dev-key-123" \
|
|
|
- -d '{"deviceId":"test-device","apiKey":"dev-key-123","status":"online","storageFreeGb":50,"capturesToday":12,"firmwareVersion":"1.0.0"}'
|
|
|
|
|
|
|
+ -d '{"deviceId":"test-device","status":"online","storageFreeGb":50,"capturesToday":12,"firmwareVersion":"1.0.0"}'
|
|
|
```
|
|
```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## TODO
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **DB migration + seed** trên Pi (`drizzle-kit push` + tạo org/project seed)
|
|
|
|
|
+- [ ] **Dashboard Pending Devices page** — approve/reject/assign device trên UI
|
|
|
|
|
+- [ ] **Worker thực** — BullMQ capture queue processing (thumbnail, validate)
|
|
|
|
|
+- [ ] **S3/MinIO** thay local storage
|
|
|
|
|
+- [ ] **Video generation** — ghép ảnh thành video
|
|
|
|
|
+- [ ] **Google OAuth thật**
|
|
|
|
|
+- [ ] **Alert rules engine**
|
|
|
|
|
+- [ ] **Dashboard captures gallery** — xem ảnh đã upload
|
|
|
|
|
+- [ ] **Pi 4 IP** — cập nhật Gogs webhook URL
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Nhật ký phát triển
|
|
|
|
|
+
|
|
|
|
|
+### 2026-03-26 — Simulator container + Captures upload API
|
|
|
|
|
+- Thêm `CapturesModule`: `POST /v1/captures/upload` (multipart, lưu local `/uploads/captures/`), `POST /v1/captures/register` (metadata only), các GET endpoints (JWT auth)
|
|
|
|
|
+- Upload lưu vào `/uploads/captures/{projectId}/{YYYY-MM-DD}/{id}.jpg`, tính SHA256 checksum
|
|
|
|
|
+- Realtime event `capture.uploaded` emit khi upload thành công
|
|
|
|
|
+- Tạo `apps/simulator/` — container Python tự động: claim → approve-internal → heartbeat + Pillow JPEG upload
|
|
|
|
|
+- Thêm `POST /v1/devices/pending/:code/approve-internal` endpoint (X-Internal-Key auth) cho simulator
|
|
|
|
|
+- `docker-compose.yml`: thêm `timelapse_uploads` volume, `SIM_INTERNAL_KEY` env, `simulator` service
|
|
|
|
|
+- api-server Dockerfile: tạo `/uploads/captures` directory
|
|
|
|
|
+- Commit: `f3b3091 feat: add simulator container + captures upload API`
|
|
|
|
|
+
|
|
|
|
|
+### 2026-03-?? — Realtime WebSocket + Device Provisioning
|
|
|
|
|
+- Socket.io gateway tại `src/realtime/gateways/realtime.gateway.ts`
|
|
|
|
|
+- Events: `device.heartbeat`, `device.status.changed`, `capture.uploaded`, `alert.opened`
|
|
|
|
|
+- Dashboard subscribe theo project room: `subscribe:project`
|
|
|
|
|
+- Device provisioning: claim → approve → bootstrap API key (bcrypt hashed)
|
|
|
|
|
+- `approveDeviceInternal()` auto-assign first project nếu không có projectId
|
|
|
|
|
+- Commit: device provisioning flow
|
|
|
|
|
+
|
|
|
|
|
+### 2026-03-?? — RBAC + Auth hoàn chỉnh
|
|
|
|
|
+- Register/Login/Refresh/Logout + JWT + bcrypt
|
|
|
|
|
+- Dashboard login/register/protected routes
|
|
|
|
|
+- Orgs CRUD + Membership management (org_admin/project_manager/viewer)
|
|
|
|
|
+- Projects CRUD với role enforcement
|
|
|
|
|
+- Commit: RBAC + dashboard auth
|
|
|
|
|
+
|
|
|
|
|
+### 2026-03-?? — Device agent (Pi thật)
|
|
|
|
|
+- `apps/device-agent/agent/main.py` — claim, poll, heartbeat loop, thermal/disk metrics
|
|
|
|
|
+- `systemd/timelapse-agent.service`
|
|
|
|
|
+- `scripts/install-agent.sh` (Pi-only, kiểm tra EUID)
|
|
|
|
|
+- `mock-device-cli.py` — claim + heartbeat simulator cho MacOS dev
|
|
|
|
|
+- Commit: `515619f`, `636ae53`
|
|
|
|
|
+
|
|
|
|
|
+### 2026-03-?? — Setup ban đầu
|
|
|
|
|
+- Scaffold NestJS api-server + Next.js dashboard + worker stub
|
|
|
|
|
+- Docker Compose (postgres, redis, 3 services)
|
|
|
|
|
+- Drizzle schema với 16 tables
|
|
|
|
|
+- Realtime module (stub)
|
|
|
|
|
+- Commit đầu tiên
|