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).
https://git.k9tech.space/kingkong/timelapse-2a6224640fb6f576f6cba46f0e1646500c87226b6 (Personal Access Token)--registry https://registry.npmjs.org/ khi cài packageMacOS (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).
"workspaces": ["apps/api-server", "apps/web-dashboard", "apps/worker", "packages/*"]
device-agent và simulator là Python — KHÔNG đưa vào workspaces@shared/types, không phải @shared-types| # | 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 |
apps/api-server/src/db/schema.tspending), capture_status, video_status, alert_severity, alert_type, alert_state, user_role, command_result_status| 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 |
| Role | Quyền |
|---|---|
org_admin |
Full: org/project/member CRUD |
project_manager |
Tạo/sửa project |
viewer |
Read-only |
/v1/auth/)POST register, POST login, POST refresh, POST logout, GET me, POST magic-link
/v1/orgs/)POST, GET, GET /:id/members, POST /:id/members, PATCH /:id/members/:userId, DELETE /:id/members/:userId
/v1/projects/)POST, GET, GET /:id, PATCH /:id, DELETE /:id — đều có JwtAuthGuard
/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 |
/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)Events: device.heartbeat, device.status.changed, capture.uploaded, alert.opened
Dashboard subscribe theo project room: project:X
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 ─────────>│
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
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:
/approve-internal (X-Internal-Key auth)/claim/:code/status → nhận bootstrap API key/v1/captures/uploadFiles:
apps/simulator/simulator/main.pyapps/simulator/Dockerfileapps/simulator/requirements.txtLưu ý: Cần ít nhất 1 project trong DB để simulator tự assign. Chạy seed trước.
apps/device-agent)Chạy trên Pi 4 thật + DSLR:
agent/main.py — claim, poll, heartbeat, đọc thermal/disk metricssystemd/timelapse-agent.service — systemd unitscripts/install-agent.sh — Pi-only installer (kiểm tra EUID)/etc/timelapse/agent.env/boot/timelapse/agent.key (chmod 600)/boot/timelapse/agent.jsonapps/device-agent/mock-device-cli.py)Dùng cho test nhanh trên MacOS (không cần Docker):
# 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
# List repos
curl -H "Authorization: token <TOKEN>" https://git.k9tech.space/api/v1/user/repos
# Create webhook
curl -X POST -H "Authorization: token <TOKEN>" \
-H "Content-Type: application/json" \
-d '{"type":"gogs","active":true,"config":{"url":"http://<pi>:9000/webhook","content_type":"json"},"events":["push"]}' \
https://git.k9tech.space/api/v1/repos/<user>/<repo>/hooks
# Push với token
git remote set-url origin "https://<TOKEN>@git.k9tech.space/kingkong/timelapse-2.git"
git push -u origin main
docker compose up -d postgres redis
docker compose up -d --build api-server web-dashboard worker simulator
Volumes:
postgres_data — PostgreSQL dataredis_data — Redis datatimelapse_uploads — Captured images (gắn vào api-server /uploads/captures)Env mới:
UPLOAD_DIR=/uploads/captures (api-server)UPLOAD_BASE_URL (api-server — URL prefix cho ảnh, mặc định http://localhost:3001)SIM_INTERNAL_KEY (api-server + simulator)cd /opt/timelapse
git pull
# Rebuild
docker compose up -d --build
# Logs
docker compose logs -f simulator
docker compose logs -f api-server
# DB migration (cần chạy 1 lần sau khi schema thay đổi)
docker compose exec api-server bun run migrate
# Cài deps
bun install --registry https://registry.npmjs.org/
# Dev
cd apps/api-server && bun run dev
cd apps/web-dashboard && bun run dev
# Build verify
cd apps/api-server && bun run build
cd apps/web-dashboard && bun run build
# Test heartbeat
curl -X POST http://localhost:3001/v1/devices/test-device/heartbeat \
-H "Content-Type: application/json" \
-H "X-API-Key: dev-key-123" \
-d '{"deviceId":"test-device","status":"online","storageFreeGb":50,"capturesToday":12,"firmwareVersion":"1.0.0"}'
drizzle-kit push + tạo org/project seed)Backend:
GET /uploads/captures/* → file system (NestExpressApplication)UPLOAD_BASE_URL env: prefix URL cho ảnh, mặc định http://localhost:3001imageUrl field (full URL)PATCH /v1/devices/:id/config — update device config (JWT auth)UpdateDeviceConfigDto: captureInterval/resolution/quality/storage/heartbeat/nightMode/timezoneDashboard (devices/[id].tsx):
290f089 feat: device config form + image preview gallery + static file servingTrên Pi: rebuild để thấy ảnh placeholder từ simulator.
Cần set UPLOAD_BASE_URL = URL thật của Pi trong .env:
UPLOAD_BASE_URL=http://<pi-ip>:3001
CapturesModule: POST /v1/captures/upload (multipart, lưu local /uploads/captures/), POST /v1/captures/register (metadata only), các GET endpoints (JWT auth)/uploads/captures/{projectId}/{YYYY-MM-DD}/{id}.jpg, tính SHA256 checksumcapture.uploaded emit khi upload thành côngapps/simulator/ — container Python tự động: claim → approve-internal → heartbeat + Pillow JPEG uploadPOST /v1/devices/pending/:code/approve-internal endpoint (X-Internal-Key auth) cho simulatordocker-compose.yml: thêm timelapse_uploads volume, SIM_INTERNAL_KEY env, simulator service/uploads/captures directoryf3b3091 feat: add simulator container + captures upload APIsrc/realtime/gateways/realtime.gateway.tsdevice.heartbeat, device.status.changed, capture.uploaded, alert.openedsubscribe:projectapproveDeviceInternal() auto-assign first project nếu không có projectIdapps/device-agent/agent/main.py — claim, poll, heartbeat loop, thermal/disk metricssystemd/timelapse-agent.servicescripts/install-agent.sh (Pi-only, kiểm tra EUID)mock-device-cli.py — claim + heartbeat simulator cho MacOS dev515619f, 636ae53