fix(admin-web): proxy routes + pages use cookie-based auth
- Add getCurrentUserFromRequest() to auth-server.ts (checks cookies first, then Authorization header)
- Update all 34 proxy routes: getCurrentUser(req.headers.get('authorization')) → getCurrentUserFromRequest(req)
- Add createProxyFetch() shared helper in lib/proxy-fetch.ts (injects auth + product-id headers)
- Update 15 admin pages: replace inline fetch helpers with createProxyFetch
- Root cause: newer pages used bare fetch() without Authorization headers, causing 401s on all proxy routes
This commit is contained in:
parent
f9fa583cae
commit
ce0074d6ee
@ -1,9 +1,9 @@
|
|||||||
Last refresh: 2026-03-21T06:00:10Z (2026-03-20 23:00:10 PDT)
|
Last refresh: 2026-03-21T21:44:43Z (2026-03-21 14:44:44 PDT)
|
||||||
Cascade conversations: 50 (411M)
|
Cascade conversations: 50 (387M)
|
||||||
Memories: 91
|
Memories: 95
|
||||||
Implicit context: 20
|
Implicit context: 20
|
||||||
Code tracker dirs: 132
|
Code tracker dirs: 89
|
||||||
File edit history: 3365 entries
|
File edit history: 3457 entries
|
||||||
Workspace storage: 35 workspaces
|
Workspace storage: 36 workspaces
|
||||||
Repo docs: 7 files across 2 repos
|
Repo docs: 7 files across 2 repos
|
||||||
Repo workflows: 43 files across 10 repos
|
Repo workflows: 43 files across 10 repos
|
||||||
|
|||||||
@ -5,7 +5,6 @@ Product-specific workflows for ChronoMind AI-powered contextual clock.
|
|||||||
## Repo-management Workflows
|
## Repo-management Workflows
|
||||||
|
|
||||||
Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`:
|
Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`:
|
||||||
|
|
||||||
- `/repo_sync-repos` — Pull latest from all repos
|
- `/repo_sync-repos` — Pull latest from all repos
|
||||||
- `/repo_commit-workspace` — Commit changes across repos
|
- `/repo_commit-workspace` — Commit changes across repos
|
||||||
- `/repo_backup-main-branch` — Backup main branches
|
- `/repo_backup-main-branch` — Backup main branches
|
||||||
|
|||||||
@ -9,16 +9,16 @@ Auto-discovers new repos, updates symlinks, and re-copies docs + workflows.
|
|||||||
|
|
||||||
## Covered Repos (All 8 workspaces)
|
## Covered Repos (All 8 workspaces)
|
||||||
|
|
||||||
| Repo | Product | Workflows | Docs |
|
| Repo | Product | Workflows | Docs |
|
||||||
| ----------------------------------- | ---------------- | --------- | ---- |
|
|------|---------|-----------|------|
|
||||||
| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ |
|
| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ |
|
||||||
| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ |
|
| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ |
|
||||||
| `learning_ai_clock` | ChronoMind | ✅ | — |
|
| `learning_ai_clock` | ChronoMind | ✅ | — |
|
||||||
| `learning_ai_peakpulse` | PeakPulse | ✅ | — |
|
| `learning_ai_peakpulse` | PeakPulse | ✅ | — |
|
||||||
| `learning_ai_fastgap` | NomGap | ✅ | — |
|
| `learning_ai_fastgap` | NomGap | ✅ | — |
|
||||||
| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — |
|
| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — |
|
||||||
| `learning_ai_common_plat` | Common Platform | ✅ | — |
|
| `learning_ai_common_plat` | Common Platform | ✅ | — |
|
||||||
| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — |
|
| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — |
|
||||||
|
|
||||||
## Steps
|
## Steps
|
||||||
|
|
||||||
|
|||||||
@ -8,16 +8,16 @@ Regenerates all 8 AI agent configuration files across all repos in the workspace
|
|||||||
|
|
||||||
## Files Generated Per Repo
|
## Files Generated Per Repo
|
||||||
|
|
||||||
| File | Tool |
|
| File | Tool |
|
||||||
| --------------------------------- | ----------------------------------------------- |
|
|------|------|
|
||||||
| `AGENTS.md` | Universal (OpenAI Codex, Claude, Copilot, etc.) |
|
| `AGENTS.md` | Universal (OpenAI Codex, Claude, Copilot, etc.) |
|
||||||
| `CLAUDE.md` | Claude Code |
|
| `CLAUDE.md` | Claude Code |
|
||||||
| `.cursorrules` | Cursor AI |
|
| `.cursorrules` | Cursor AI |
|
||||||
| `.github/copilot-instructions.md` | GitHub Copilot |
|
| `.github/copilot-instructions.md` | GitHub Copilot |
|
||||||
| `.windsurfrules` | Windsurf / Cascade |
|
| `.windsurfrules` | Windsurf / Cascade |
|
||||||
| `.clinerules` | Cline / Roo Code |
|
| `.clinerules` | Cline / Roo Code |
|
||||||
| `.aider.conf.yml` | Aider |
|
| `.aider.conf.yml` | Aider |
|
||||||
| `.editorconfig` | All editors |
|
| `.editorconfig` | All editors |
|
||||||
|
|
||||||
## Steps
|
## Steps
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,6 @@ Product-specific workflows for NomGap fasting visualization app.
|
|||||||
## Repo-management Workflows
|
## Repo-management Workflows
|
||||||
|
|
||||||
Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`:
|
Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`:
|
||||||
|
|
||||||
- `/repo_sync-repos` — Pull latest from all repos
|
- `/repo_sync-repos` — Pull latest from all repos
|
||||||
- `/repo_commit-workspace` — Commit changes across repos
|
- `/repo_commit-workspace` — Commit changes across repos
|
||||||
- `/repo_backup-main-branch` — Backup main branches
|
- `/repo_backup-main-branch` — Backup main branches
|
||||||
|
|||||||
@ -9,16 +9,16 @@ Auto-discovers new repos, updates symlinks, and re-copies docs + workflows.
|
|||||||
|
|
||||||
## Covered Repos (All 8 workspaces)
|
## Covered Repos (All 8 workspaces)
|
||||||
|
|
||||||
| Repo | Product | Workflows | Docs |
|
| Repo | Product | Workflows | Docs |
|
||||||
| ----------------------------------- | ---------------- | --------- | ---- |
|
|------|---------|-----------|------|
|
||||||
| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ |
|
| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ |
|
||||||
| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ |
|
| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ |
|
||||||
| `learning_ai_clock` | ChronoMind | ✅ | — |
|
| `learning_ai_clock` | ChronoMind | ✅ | — |
|
||||||
| `learning_ai_peakpulse` | PeakPulse | ✅ | — |
|
| `learning_ai_peakpulse` | PeakPulse | ✅ | — |
|
||||||
| `learning_ai_fastgap` | NomGap | ✅ | — |
|
| `learning_ai_fastgap` | NomGap | ✅ | — |
|
||||||
| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — |
|
| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — |
|
||||||
| `learning_ai_common_plat` | Common Platform | ✅ | — |
|
| `learning_ai_common_plat` | Common Platform | ✅ | — |
|
||||||
| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — |
|
| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — |
|
||||||
|
|
||||||
## Steps
|
## Steps
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,6 @@ Product-specific workflows for FlowMonk, the agent-first planning and execution
|
|||||||
## Repo-management Workflows
|
## Repo-management Workflows
|
||||||
|
|
||||||
Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`:
|
Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`:
|
||||||
|
|
||||||
- `/repo_sync-repos` — Pull latest from all repos
|
- `/repo_sync-repos` — Pull latest from all repos
|
||||||
- `/repo_commit-workspace` — Commit changes across repos
|
- `/repo_commit-workspace` — Commit changes across repos
|
||||||
- `/repo_backup-main-branch` — Backup main branches
|
- `/repo_backup-main-branch` — Backup main branches
|
||||||
|
|||||||
@ -5,7 +5,6 @@ Product-specific workflows for JarvisJr voice coaching app.
|
|||||||
## Repo-management Workflows
|
## Repo-management Workflows
|
||||||
|
|
||||||
Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`:
|
Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`:
|
||||||
|
|
||||||
- `/repo_sync-repos` — Pull latest from all repos
|
- `/repo_sync-repos` — Pull latest from all repos
|
||||||
- `/repo_commit-workspace` — Commit changes across repos
|
- `/repo_commit-workspace` — Commit changes across repos
|
||||||
- `/repo_backup-main-branch` — Backup main branches
|
- `/repo_backup-main-branch` — Backup main branches
|
||||||
|
|||||||
@ -9,16 +9,16 @@ Auto-discovers new repos, updates symlinks, and re-copies docs + workflows.
|
|||||||
|
|
||||||
## Covered Repos (All 8 workspaces)
|
## Covered Repos (All 8 workspaces)
|
||||||
|
|
||||||
| Repo | Product | Workflows | Docs |
|
| Repo | Product | Workflows | Docs |
|
||||||
| ----------------------------------- | ---------------- | --------- | ---- |
|
|------|---------|-----------|------|
|
||||||
| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ |
|
| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ |
|
||||||
| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ |
|
| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ |
|
||||||
| `learning_ai_clock` | ChronoMind | ✅ | — |
|
| `learning_ai_clock` | ChronoMind | ✅ | — |
|
||||||
| `learning_ai_peakpulse` | PeakPulse | ✅ | — |
|
| `learning_ai_peakpulse` | PeakPulse | ✅ | — |
|
||||||
| `learning_ai_fastgap` | NomGap | ✅ | — |
|
| `learning_ai_fastgap` | NomGap | ✅ | — |
|
||||||
| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — |
|
| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — |
|
||||||
| `learning_ai_common_plat` | Common Platform | ✅ | — |
|
| `learning_ai_common_plat` | Common Platform | ✅ | — |
|
||||||
| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — |
|
| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — |
|
||||||
|
|
||||||
## Steps
|
## Steps
|
||||||
|
|
||||||
|
|||||||
@ -9,17 +9,14 @@ The primary interface for running audits is the **Sensor Audit Dashboard**.
|
|||||||
## Steps
|
## Steps
|
||||||
|
|
||||||
1. **Ensure the dashboard is running**
|
1. **Ensure the dashboard is running**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
/Users/sd9235/code/mygh/learning_ai_mac_tooling/audit-ctl.sh start
|
/Users/sd9235/code/mygh/learning_ai_mac_tooling/audit-ctl.sh start
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Open the dashboard**
|
2. **Open the dashboard**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
/Users/sd9235/code/mygh/learning_ai_mac_tooling/audit-ctl.sh open
|
/Users/sd9235/code/mygh/learning_ai_mac_tooling/audit-ctl.sh open
|
||||||
```
|
```
|
||||||
|
|
||||||
Navigate to **Run Audit** in the sidebar.
|
Navigate to **Run Audit** in the sidebar.
|
||||||
|
|
||||||
3. **Choose a mode in the dashboard UI**:
|
3. **Choose a mode in the dashboard UI**:
|
||||||
|
|||||||
@ -5,7 +5,6 @@ Product-specific workflows for PeakPulse sensor-driven adventure tracker.
|
|||||||
## Repo-management Workflows
|
## Repo-management Workflows
|
||||||
|
|
||||||
Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`:
|
Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`:
|
||||||
|
|
||||||
- `/repo_sync-repos` — Pull latest from all repos
|
- `/repo_sync-repos` — Pull latest from all repos
|
||||||
- `/repo_commit-workspace` — Commit changes across repos
|
- `/repo_commit-workspace` — Commit changes across repos
|
||||||
- `/repo_backup-main-branch` — Backup main branches
|
- `/repo_backup-main-branch` — Backup main branches
|
||||||
|
|||||||
@ -19,12 +19,12 @@ cd /Users/sd9235/code/mygh/learning_ai_peakpulse/ios && xcodegen generate
|
|||||||
|
|
||||||
3. Build all iOS targets:
|
3. Build all iOS targets:
|
||||||
```bash
|
```bash
|
||||||
cd /Users/sd9235/code/mygh/learning_ai_peakpulse/ios && xcodebuild -project PeakPulse.xcodeproj -scheme PeakPulse -destination 'platform=iOS Simulator,name=iPhone 16' -quiet build
|
cd /Users/sd9235/code/mygh/learning_ai_peakpulse/ios && xcodebuild -project PeakPulse.xcodeproj -scheme PeakPulse -destination 'platform=iOS Simulator,name=iPhone 17' -quiet build
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Run unit tests:
|
4. Run unit tests:
|
||||||
```bash
|
```bash
|
||||||
cd /Users/sd9235/code/mygh/learning_ai_peakpulse/ios && xcodebuild -project PeakPulse.xcodeproj -scheme PeakPulse -destination 'platform=iOS Simulator,name=iPhone 16' -quiet test
|
cd /Users/sd9235/code/mygh/learning_ai_peakpulse/ios && xcodebuild -project PeakPulse.xcodeproj -scheme PeakPulse -destination 'platform=iOS Simulator,name=iPhone 17' -quiet test
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Verify no print() statements in production code:
|
5. Verify no print() statements in production code:
|
||||||
@ -51,9 +51,9 @@ cat /Users/sd9235/code/mygh/learning_ai_peakpulse/ios/PeakPulse/PeakPulse.entitl
|
|||||||
cat /Users/sd9235/code/mygh/learning_ai_peakpulse/.env.example
|
cat /Users/sd9235/code/mygh/learning_ai_peakpulse/.env.example
|
||||||
```
|
```
|
||||||
|
|
||||||
9. Run platform-service peak-sessions tests:
|
9. Run backend tests:
|
||||||
```bash
|
```bash
|
||||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && npx vitest run services/platform-service/src/modules/peak-sessions/peak-sessions.test.ts
|
cd /Users/sd9235/code/mygh/learning_ai_peakpulse/backend && npm test
|
||||||
```
|
```
|
||||||
|
|
||||||
10. Verify CI workflow exists and is valid:
|
10. Verify CI workflow exists and is valid:
|
||||||
|
|||||||
@ -9,16 +9,16 @@ Auto-discovers new repos, updates symlinks, and re-copies docs + workflows.
|
|||||||
|
|
||||||
## Covered Repos (All 8 workspaces)
|
## Covered Repos (All 8 workspaces)
|
||||||
|
|
||||||
| Repo | Product | Workflows | Docs |
|
| Repo | Product | Workflows | Docs |
|
||||||
| ----------------------------------- | ---------------- | --------- | ---- |
|
|------|---------|-----------|------|
|
||||||
| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ |
|
| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ |
|
||||||
| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ |
|
| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ |
|
||||||
| `learning_ai_clock` | ChronoMind | ✅ | — |
|
| `learning_ai_clock` | ChronoMind | ✅ | — |
|
||||||
| `learning_ai_peakpulse` | PeakPulse | ✅ | — |
|
| `learning_ai_peakpulse` | PeakPulse | ✅ | — |
|
||||||
| `learning_ai_fastgap` | NomGap | ✅ | — |
|
| `learning_ai_fastgap` | NomGap | ✅ | — |
|
||||||
| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — |
|
| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — |
|
||||||
| `learning_ai_common_plat` | Common Platform | ✅ | — |
|
| `learning_ai_common_plat` | Common Platform | ✅ | — |
|
||||||
| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — |
|
| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — |
|
||||||
|
|
||||||
## Steps
|
## Steps
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,6 @@ Product-specific workflows for MindLyst role-based life OS.
|
|||||||
## Repo-management Workflows
|
## Repo-management Workflows
|
||||||
|
|
||||||
Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`:
|
Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`:
|
||||||
|
|
||||||
- `/repo_sync-repos` — Pull latest from all repos
|
- `/repo_sync-repos` — Pull latest from all repos
|
||||||
- `/repo_commit-workspace` — Commit changes across repos
|
- `/repo_commit-workspace` — Commit changes across repos
|
||||||
- `/repo_backup-main-branch` — Backup main branches
|
- `/repo_backup-main-branch` — Backup main branches
|
||||||
|
|||||||
@ -9,16 +9,16 @@ Auto-discovers new repos, updates symlinks, and re-copies docs + workflows.
|
|||||||
|
|
||||||
## Covered Repos (All 8 workspaces)
|
## Covered Repos (All 8 workspaces)
|
||||||
|
|
||||||
| Repo | Product | Workflows | Docs |
|
| Repo | Product | Workflows | Docs |
|
||||||
| ----------------------------------- | ---------------- | --------- | ---- |
|
|------|---------|-----------|------|
|
||||||
| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ |
|
| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ |
|
||||||
| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ |
|
| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ |
|
||||||
| `learning_ai_clock` | ChronoMind | ✅ | — |
|
| `learning_ai_clock` | ChronoMind | ✅ | — |
|
||||||
| `learning_ai_peakpulse` | PeakPulse | ✅ | — |
|
| `learning_ai_peakpulse` | PeakPulse | ✅ | — |
|
||||||
| `learning_ai_fastgap` | NomGap | ✅ | — |
|
| `learning_ai_fastgap` | NomGap | ✅ | — |
|
||||||
| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — |
|
| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — |
|
||||||
| `learning_ai_common_plat` | Common Platform | ✅ | — |
|
| `learning_ai_common_plat` | Common Platform | ✅ | — |
|
||||||
| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — |
|
| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — |
|
||||||
|
|
||||||
## Steps
|
## Steps
|
||||||
|
|
||||||
|
|||||||
@ -49,7 +49,6 @@ See `iosApp/README_SETUP.md` for more details.
|
|||||||
### 4. Verify Gradle/KMP builds
|
### 4. Verify Gradle/KMP builds
|
||||||
|
|
||||||
// turbo
|
// turbo
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd mindlyst-native && ./gradlew :shared:compileKotlinIosSimulatorArm64
|
cd mindlyst-native && ./gradlew :shared:compileKotlinIosSimulatorArm64
|
||||||
```
|
```
|
||||||
@ -67,7 +66,6 @@ cd mindlyst-native && ./gradlew :shared:compileKotlinIosArm64
|
|||||||
```
|
```
|
||||||
|
|
||||||
For simulator testing:
|
For simulator testing:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd mindlyst-native && ./gradlew :shared:compileKotlinIosSimulatorArm64
|
cd mindlyst-native && ./gradlew :shared:compileKotlinIosSimulatorArm64
|
||||||
```
|
```
|
||||||
@ -75,7 +73,6 @@ cd mindlyst-native && ./gradlew :shared:compileKotlinIosSimulatorArm64
|
|||||||
### 2. Embed the shared framework in Xcode
|
### 2. Embed the shared framework in Xcode
|
||||||
|
|
||||||
If not already linked:
|
If not already linked:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd mindlyst-native && ./gradlew :shared:embedAndSignAppleFrameworkForXcode
|
cd mindlyst-native && ./gradlew :shared:embedAndSignAppleFrameworkForXcode
|
||||||
```
|
```
|
||||||
@ -90,7 +87,6 @@ Or manually: open Xcode → Target → General → Build.
|
|||||||
### 4. Clean build folder
|
### 4. Clean build folder
|
||||||
|
|
||||||
// turbo
|
// turbo
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
xcodebuild clean -project mindlyst-native/MindLyst.xcodeproj -scheme MindLyst -configuration Release 2>&1 | tail -3
|
xcodebuild clean -project mindlyst-native/MindLyst.xcodeproj -scheme MindLyst -configuration Release 2>&1 | tail -3
|
||||||
```
|
```
|
||||||
@ -144,24 +140,24 @@ The `app-store-connect` export method auto-uploads the IPA.
|
|||||||
|
|
||||||
### Key Paths
|
### Key Paths
|
||||||
|
|
||||||
| Path | Purpose |
|
| Path | Purpose |
|
||||||
| ------------------------------------------- | ---------------------------------------- |
|
|------|---------|
|
||||||
| `mindlyst-native/iosApp/` | Swift UI source files |
|
| `mindlyst-native/iosApp/` | Swift UI source files |
|
||||||
| `mindlyst-native/shared/` | KMP shared module (business logic) |
|
| `mindlyst-native/shared/` | KMP shared module (business logic) |
|
||||||
| `mindlyst-native/shared/build.gradle.kts` | KMP build config (iOS targets) |
|
| `mindlyst-native/shared/build.gradle.kts` | KMP build config (iOS targets) |
|
||||||
| `mindlyst-native/gradle/libs.versions.toml` | Version catalog |
|
| `mindlyst-native/gradle/libs.versions.toml` | Version catalog |
|
||||||
| `mindlyst-native/MindLyst.xcodeproj/` | Xcode project (must be created manually) |
|
| `mindlyst-native/MindLyst.xcodeproj/` | Xcode project (must be created manually) |
|
||||||
| `scripts/MindLystExportOptions.plist` | Export options for TestFlight upload |
|
| `scripts/MindLystExportOptions.plist` | Export options for TestFlight upload |
|
||||||
|
|
||||||
### Build Identity
|
### Build Identity
|
||||||
|
|
||||||
| Field | Value |
|
| Field | Value |
|
||||||
| ------------- | ----------------------- |
|
|-------|-------|
|
||||||
| Team ID | `748N7QPX7J` |
|
| Team ID | `748N7QPX7J` |
|
||||||
| Bundle ID | `com.mindlyst.MindLyst` |
|
| Bundle ID | `com.mindlyst.MindLyst` |
|
||||||
| Signing | Automatic |
|
| Signing | Automatic |
|
||||||
| KMP Framework | `shared` (static) |
|
| KMP Framework | `shared` (static) |
|
||||||
| Min iOS | 16.0 |
|
| Min iOS | 16.0 |
|
||||||
|
|
||||||
### KMP Architecture
|
### KMP Architecture
|
||||||
|
|
||||||
@ -177,12 +173,12 @@ All business logic lives in `shared/src/commonMain/`. iOS code in `iosApp/` is a
|
|||||||
|
|
||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
|
|
||||||
| Problem | Fix |
|
| Problem | Fix |
|
||||||
| ----------------------------- | ------------------------------------------------------------------------ |
|
|---------|-----|
|
||||||
| "No signing certificate" | Xcode → Settings → Accounts → Manage Certificates → + Apple Distribution |
|
| "No signing certificate" | Xcode → Settings → Accounts → Manage Certificates → + Apple Distribution |
|
||||||
| "Provisioning profile" error | Xcode → Target → Signing → Enable "Automatically manage signing" |
|
| "Provisioning profile" error | Xcode → Target → Signing → Enable "Automatically manage signing" |
|
||||||
| "Build number already exists" | Increment build number in step 3 |
|
| "Build number already exists" | Increment build number in step 3 |
|
||||||
| KMP build fails | Check Java 17: `java -version`. Install: `brew install openjdk@17` |
|
| KMP build fails | Check Java 17: `java -version`. Install: `brew install openjdk@17` |
|
||||||
| "shared.framework not found" | Run `./gradlew :shared:embedAndSignAppleFrameworkForXcode` |
|
| "shared.framework not found" | Run `./gradlew :shared:embedAndSignAppleFrameworkForXcode` |
|
||||||
| Gradle SSL proxy error | Build on home network (corporate proxy blocks Gradle repos) |
|
| Gradle SSL proxy error | Build on home network (corporate proxy blocks Gradle repos) |
|
||||||
| Processing >30 min | Check [Apple system status](https://developer.apple.com/system-status/) |
|
| Processing >30 min | Check [Apple system status](https://developer.apple.com/system-status/) |
|
||||||
|
|||||||
@ -5,7 +5,6 @@ Product-specific workflows for LysnrAI voice-to-text dictation platform.
|
|||||||
## Repo-management Workflows
|
## Repo-management Workflows
|
||||||
|
|
||||||
Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`:
|
Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`:
|
||||||
|
|
||||||
- `/repo_sync-repos` — Pull latest from all repos
|
- `/repo_sync-repos` — Pull latest from all repos
|
||||||
- `/repo_commit-workspace` — Commit changes across repos
|
- `/repo_commit-workspace` — Commit changes across repos
|
||||||
- `/repo_backup-main-branch` — Backup main branches
|
- `/repo_backup-main-branch` — Backup main branches
|
||||||
|
|||||||
@ -9,16 +9,16 @@ Auto-discovers new repos, updates symlinks, and re-copies docs + workflows.
|
|||||||
|
|
||||||
## Covered Repos (All 8 workspaces)
|
## Covered Repos (All 8 workspaces)
|
||||||
|
|
||||||
| Repo | Product | Workflows | Docs |
|
| Repo | Product | Workflows | Docs |
|
||||||
| ----------------------------------- | ---------------- | --------- | ---- |
|
|------|---------|-----------|------|
|
||||||
| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ |
|
| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ |
|
||||||
| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ |
|
| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ |
|
||||||
| `learning_ai_clock` | ChronoMind | ✅ | — |
|
| `learning_ai_clock` | ChronoMind | ✅ | — |
|
||||||
| `learning_ai_peakpulse` | PeakPulse | ✅ | — |
|
| `learning_ai_peakpulse` | PeakPulse | ✅ | — |
|
||||||
| `learning_ai_fastgap` | NomGap | ✅ | — |
|
| `learning_ai_fastgap` | NomGap | ✅ | — |
|
||||||
| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — |
|
| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — |
|
||||||
| `learning_ai_common_plat` | Common Platform | ✅ | — |
|
| `learning_ai_common_plat` | Common Platform | ✅ | — |
|
||||||
| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — |
|
| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — |
|
||||||
|
|
||||||
## Steps
|
## Steps
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { createProxyFetch } from '@/lib/proxy-fetch';
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
FlaskConical,
|
FlaskConical,
|
||||||
@ -67,13 +69,7 @@ function formatDate(iso: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function apiFetch(path: string, opts?: RequestInit) {
|
const apiFetch = createProxyFetch('/api/agent-evals');
|
||||||
const res = await fetch(`/api/agent-evals/${path}`, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
...opts,
|
|
||||||
});
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AgentEvalsPage() {
|
export default function AgentEvalsPage() {
|
||||||
const [suites, setSuites] = useState<EvalSuite[]>([]);
|
const [suites, setSuites] = useState<EvalSuite[]>([]);
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { createProxyFetch } from '@/lib/proxy-fetch';
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { Coins, Plus, MoreHorizontal, AlertTriangle, TrendingUp, Trash2 } from 'lucide-react';
|
import { Coins, Plus, MoreHorizontal, AlertTriangle, TrendingUp, Trash2 } from 'lucide-react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@ -73,13 +75,7 @@ function formatDate(iso: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function apiFetch(path: string, opts?: RequestInit) {
|
const apiFetch = createProxyFetch('/api/ai-budgets');
|
||||||
const res = await fetch(`/api/ai-budgets/${path}`, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
...opts,
|
|
||||||
});
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AIBudgetsPage() {
|
export default function AIBudgetsPage() {
|
||||||
const [policies, setPolicies] = useState<BudgetPolicy[]>([]);
|
const [policies, setPolicies] = useState<BudgetPolicy[]>([]);
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { createProxyFetch } from '@/lib/proxy-fetch';
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { Mail, RotateCcw } from 'lucide-react';
|
import { Mail, RotateCcw } from 'lucide-react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@ -49,13 +51,7 @@ function formatDate(iso: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function apiFetch(path: string, opts?: RequestInit) {
|
const apiFetch = createProxyFetch('/api/delivery');
|
||||||
const res = await fetch(`/api/delivery/${path}`, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
...opts,
|
|
||||||
});
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function DeliveryPage() {
|
export default function DeliveryPage() {
|
||||||
const [entries, setEntries] = useState<DeliveryEntry[]>([]);
|
const [entries, setEntries] = useState<DeliveryEntry[]>([]);
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { createProxyFetch } from '@/lib/proxy-fetch';
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { Radio, Plus, MoreHorizontal, Trash2 } from 'lucide-react';
|
import { Radio, Plus, MoreHorizontal, Trash2 } from 'lucide-react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@ -47,13 +49,7 @@ function formatDate(iso: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function apiFetch(path: string, opts?: RequestInit) {
|
const apiFetch = createProxyFetch('/api/event-subscriptions');
|
||||||
const res = await fetch(`/api/event-subscriptions/${path}`, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
...opts,
|
|
||||||
});
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function EventSubscriptionsPage() {
|
export default function EventSubscriptionsPage() {
|
||||||
const [subs, setSubs] = useState<EventSub[]>([]);
|
const [subs, setSubs] = useState<EventSub[]>([]);
|
||||||
@ -65,7 +61,7 @@ export default function EventSubscriptionsPage() {
|
|||||||
|
|
||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const data = await apiFetch('list');
|
const data = await apiFetch('');
|
||||||
setSubs(
|
setSubs(
|
||||||
Array.isArray(data?.subscriptions) ? data.subscriptions : Array.isArray(data) ? data : []
|
Array.isArray(data?.subscriptions) ? data.subscriptions : Array.isArray(data) ? data : []
|
||||||
);
|
);
|
||||||
@ -78,7 +74,7 @@ export default function EventSubscriptionsPage() {
|
|||||||
|
|
||||||
async function handleCreate() {
|
async function handleCreate() {
|
||||||
setCreating(true);
|
setCreating(true);
|
||||||
await apiFetch('create', {
|
await apiFetch('', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ eventType: newEvent, callbackUrl: newUrl }),
|
body: JSON.stringify({ eventType: newEvent, callbackUrl: newUrl }),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { createProxyFetch } from '@/lib/proxy-fetch';
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { ShieldBan, Plus, Trash2 } from 'lucide-react';
|
import { ShieldBan, Plus, Trash2 } from 'lucide-react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@ -48,13 +50,7 @@ function formatDate(iso: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function apiFetch(path: string, opts?: RequestInit) {
|
const apiFetch = createProxyFetch('/api/ip-rules');
|
||||||
const res = await fetch(`/api/ip-rules/${path}`, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
...opts,
|
|
||||||
});
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function IpRulesPage() {
|
export default function IpRulesPage() {
|
||||||
const [rules, setRules] = useState<IpRule[]>([]);
|
const [rules, setRules] = useState<IpRule[]>([]);
|
||||||
@ -67,7 +63,7 @@ export default function IpRulesPage() {
|
|||||||
|
|
||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const data = await apiFetch('list');
|
const data = await apiFetch('');
|
||||||
setRules(Array.isArray(data?.rules) ? data.rules : Array.isArray(data) ? data : []);
|
setRules(Array.isArray(data?.rules) ? data.rules : Array.isArray(data) ? data : []);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}, []);
|
}, []);
|
||||||
@ -78,7 +74,7 @@ export default function IpRulesPage() {
|
|||||||
|
|
||||||
async function handleCreate() {
|
async function handleCreate() {
|
||||||
setCreating(true);
|
setCreating(true);
|
||||||
await apiFetch('create', {
|
await apiFetch('', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ cidr: newCidr, type: newType, reason: newReason || undefined }),
|
body: JSON.stringify({ cidr: newCidr, type: newType, reason: newReason || undefined }),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/components/ui/dropdown-menu';
|
} from '@/components/ui/dropdown-menu';
|
||||||
|
import { createProxyFetch } from '@/lib/proxy-fetch';
|
||||||
|
|
||||||
interface Job {
|
interface Job {
|
||||||
id: string;
|
id: string;
|
||||||
@ -58,21 +59,8 @@ function formatDate(iso: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function jobsFetch(path: string, opts?: RequestInit) {
|
const jobsFetch = createProxyFetch('/api/jobs');
|
||||||
const res = await fetch(`/api/jobs/${path}`, {
|
const runsFetch = createProxyFetch('/api/runs');
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
...opts,
|
|
||||||
});
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function runsFetch(path: string, opts?: RequestInit) {
|
|
||||||
const res = await fetch(`/api/runs/${path}`, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
...opts,
|
|
||||||
});
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function JobsPage() {
|
export default function JobsPage() {
|
||||||
const [jobs, setJobs] = useState<Job[]>([]);
|
const [jobs, setJobs] = useState<Job[]>([]);
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { createProxyFetch } from '@/lib/proxy-fetch';
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { BookOpen, Plus, MoreHorizontal, Search, FileText, Trash2, Upload } from 'lucide-react';
|
import { BookOpen, Plus, MoreHorizontal, Search, FileText, Trash2, Upload } from 'lucide-react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@ -49,13 +51,7 @@ function formatDate(iso: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function apiFetch(path: string, opts?: RequestInit) {
|
const apiFetch = createProxyFetch('/api/knowledge');
|
||||||
const res = await fetch(`/api/knowledge/${path}`, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
...opts,
|
|
||||||
});
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function KnowledgePage() {
|
export default function KnowledgePage() {
|
||||||
const [bases, setBases] = useState<KnowledgeBase[]>([]);
|
const [bases, setBases] = useState<KnowledgeBase[]>([]);
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
|
import { createProxyFetch } from '@/lib/proxy-fetch';
|
||||||
|
|
||||||
interface MaintenanceStatus {
|
interface MaintenanceStatus {
|
||||||
mode: string;
|
mode: string;
|
||||||
@ -47,13 +48,7 @@ const modeConfig: Record<string, { label: string; color: string; description: st
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async function apiFetch(path: string, opts?: RequestInit) {
|
const apiFetch = createProxyFetch('/api/maintenance');
|
||||||
const res = await fetch(`/api/maintenance/${path}`, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
...opts,
|
|
||||||
});
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function MaintenancePage() {
|
export default function MaintenancePage() {
|
||||||
const [status, setStatus] = useState<MaintenanceStatus>({ mode: 'off' });
|
const [status, setStatus] = useState<MaintenanceStatus>({ mode: 'off' });
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { createProxyFetch } from '@/lib/proxy-fetch';
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { Store, MoreHorizontal, Search, ThumbsUp, ThumbsDown } from 'lucide-react';
|
import { Store, MoreHorizontal, Search, ThumbsUp, ThumbsDown } from 'lucide-react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@ -49,13 +51,7 @@ function formatDate(iso: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function apiFetch(path: string, opts?: RequestInit) {
|
const apiFetch = createProxyFetch('/api/marketplace');
|
||||||
const res = await fetch(`/api/marketplace/${path}`, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
...opts,
|
|
||||||
});
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function MarketplacePage() {
|
export default function MarketplacePage() {
|
||||||
const [listings, setListings] = useState<Listing[]>([]);
|
const [listings, setListings] = useState<Listing[]>([]);
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { createProxyFetch } from '@/lib/proxy-fetch';
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { Building2, Plus, MoreHorizontal, Users, FolderOpen, Search, Trash2 } from 'lucide-react';
|
import { Building2, Plus, MoreHorizontal, Users, FolderOpen, Search, Trash2 } from 'lucide-react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@ -71,13 +73,7 @@ function formatDate(iso: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function apiFetch(path: string, opts?: RequestInit) {
|
const apiFetch = createProxyFetch('/api/orgs');
|
||||||
const res = await fetch(`/api/orgs/${path}`, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
...opts,
|
|
||||||
});
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function OrganizationsPage() {
|
export default function OrganizationsPage() {
|
||||||
const [orgs, setOrgs] = useState<Org[]>([]);
|
const [orgs, setOrgs] = useState<Org[]>([]);
|
||||||
@ -98,7 +94,7 @@ export default function OrganizationsPage() {
|
|||||||
const loadOrgs = useCallback(async () => {
|
const loadOrgs = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const qs = search ? `?search=${encodeURIComponent(search)}` : '';
|
const qs = search ? `?search=${encodeURIComponent(search)}` : '';
|
||||||
const data = await apiFetch(`list${qs}`);
|
const data = await apiFetch(qs);
|
||||||
if (data?.organizations) {
|
if (data?.organizations) {
|
||||||
setOrgs(data.organizations);
|
setOrgs(data.organizations);
|
||||||
setTotal(data.total ?? data.organizations.length);
|
setTotal(data.total ?? data.organizations.length);
|
||||||
@ -115,7 +111,7 @@ export default function OrganizationsPage() {
|
|||||||
|
|
||||||
async function handleCreate() {
|
async function handleCreate() {
|
||||||
setCreating(true);
|
setCreating(true);
|
||||||
await apiFetch('create', {
|
await apiFetch('', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ name: newName, slug: newSlug || undefined }),
|
body: JSON.stringify({ name: newName, slug: newSlug || undefined }),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { Star, ThumbsUp, ThumbsDown, MoreHorizontal, Flag } from 'lucide-react';
|
import { Star, ThumbsUp, ThumbsDown, MoreHorizontal, Flag } from 'lucide-react';
|
||||||
|
import { createProxyFetch } from '@/lib/proxy-fetch';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@ -48,13 +49,7 @@ function formatDate(iso: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function apiFetch(path: string, opts?: RequestInit) {
|
const apiFetch = createProxyFetch('/api/reviews');
|
||||||
const res = await fetch(`/api/reviews/${path}`, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
...opts,
|
|
||||||
});
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ReviewsPage() {
|
export default function ReviewsPage() {
|
||||||
const [reviews, setReviews] = useState<Review[]>([]);
|
const [reviews, setReviews] = useState<Review[]>([]);
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { createProxyFetch } from '@/lib/proxy-fetch';
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { MonitorSmartphone, Search, Globe } from 'lucide-react';
|
import { MonitorSmartphone, Search, Globe } from 'lucide-react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@ -35,13 +37,7 @@ function formatDate(iso: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function apiFetch(path: string, opts?: RequestInit) {
|
const apiFetch = createProxyFetch('/api/sessions');
|
||||||
const res = await fetch(`/api/sessions/${path}`, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
...opts,
|
|
||||||
});
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SessionsAdminPage() {
|
export default function SessionsAdminPage() {
|
||||||
const [sessions, setSessions] = useState<SessionEntry[]>([]);
|
const [sessions, setSessions] = useState<SessionEntry[]>([]);
|
||||||
@ -51,7 +47,7 @@ export default function SessionsAdminPage() {
|
|||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const qs = search ? `?search=${encodeURIComponent(search)}` : '';
|
const qs = search ? `?search=${encodeURIComponent(search)}` : '';
|
||||||
const data = await apiFetch(`list${qs}`);
|
const data = await apiFetch(qs);
|
||||||
setSessions(Array.isArray(data?.sessions) ? data.sessions : Array.isArray(data) ? data : []);
|
setSessions(Array.isArray(data?.sessions) ? data.sessions : Array.isArray(data) ? data : []);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}, [search]);
|
}, [search]);
|
||||||
@ -62,7 +58,7 @@ export default function SessionsAdminPage() {
|
|||||||
|
|
||||||
async function handleRevoke(id: string) {
|
async function handleRevoke(id: string) {
|
||||||
if (!confirm('Revoke this session?')) return;
|
if (!confirm('Revoke this session?')) return;
|
||||||
await apiFetch(`${id}/revoke`, { method: 'POST' });
|
await apiFetch(`${id}`, { method: 'DELETE' });
|
||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { createProxyFetch } from '@/lib/proxy-fetch';
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
LifeBuoy,
|
LifeBuoy,
|
||||||
@ -83,13 +85,7 @@ function formatDate(iso: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function apiFetch(path: string, opts?: RequestInit) {
|
const apiFetch = createProxyFetch('/api/support');
|
||||||
const res = await fetch(`/api/support/${path}`, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
...opts,
|
|
||||||
});
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SupportCasesPage() {
|
export default function SupportCasesPage() {
|
||||||
const [cases, setCases] = useState<SupportCase[]>([]);
|
const [cases, setCases] = useState<SupportCase[]>([]);
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { createProxyFetch } from '@/lib/proxy-fetch';
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
ListOrdered,
|
ListOrdered,
|
||||||
@ -65,13 +67,7 @@ function formatDate(iso: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function apiFetch(path: string, opts?: RequestInit) {
|
const apiFetch = createProxyFetch('/api/waitlist');
|
||||||
const res = await fetch(`/api/waitlist/${path}`, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
...opts,
|
|
||||||
});
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function WaitlistPage() {
|
export default function WaitlistPage() {
|
||||||
const [entries, setEntries] = useState<WaitlistEntry[]>([]);
|
const [entries, setEntries] = useState<WaitlistEntry[]>([]);
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { createProxyFetch } from '@/lib/proxy-fetch';
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { Webhook, Plus, MoreHorizontal, RotateCcw, Trash2 } from 'lucide-react';
|
import { Webhook, Plus, MoreHorizontal, RotateCcw, Trash2 } from 'lucide-react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@ -57,13 +59,7 @@ function formatDate(iso: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function apiFetch(path: string, opts?: RequestInit) {
|
const apiFetch = createProxyFetch('/api/webhooks');
|
||||||
const res = await fetch(`/api/webhooks/${path}`, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
...opts,
|
|
||||||
});
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function WebhooksPage() {
|
export default function WebhooksPage() {
|
||||||
const [subs, setSubs] = useState<WebhookSub[]>([]);
|
const [subs, setSubs] = useState<WebhookSub[]>([]);
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import * as actiontrailClient from '@/lib/actiontrail-client';
|
import * as actiontrailClient from '@/lib/actiontrail-client';
|
||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) {
|
if (!caller) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,14 +3,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
const { path } = await params;
|
const { path } = await params;
|
||||||
const targetPath = `/api/agent-evals/${path.join('/')}`;
|
const targetPath = `/api/agent-evals/${path.join('/')}`;
|
||||||
|
|||||||
@ -5,14 +5,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) {
|
if (!caller) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { getCollection } from '@/lib/datastore';
|
import { getCollection } from '@/lib/datastore';
|
||||||
import { getRequestProductId } from '@/lib/product-config';
|
import { getRequestProductId } from '@/lib/product-config';
|
||||||
interface CohortRow {
|
interface CohortRow {
|
||||||
@ -37,7 +37,7 @@ function getMondayOfWeek(date: Date): string {
|
|||||||
}
|
}
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) {
|
if (!caller) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { getCollection } from '@/lib/datastore';
|
import { getCollection } from '@/lib/datastore';
|
||||||
import { getRequestProductId } from '@/lib/product-config';
|
import { getRequestProductId } from '@/lib/product-config';
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ interface RevenueResponse {
|
|||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) {
|
if (!caller) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import * as platformClient from '@/lib/platform-client';
|
import * as platformClient from '@/lib/platform-client';
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) {
|
if (!caller) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,14 +3,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
const { path } = await params;
|
const { path } = await params;
|
||||||
const targetPath = `/api/delivery/${path.join('/')}`;
|
const targetPath = `/api/delivery/${path.join('/')}`;
|
||||||
|
|||||||
@ -3,14 +3,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
const { path } = await params;
|
const { path } = await params;
|
||||||
const targetPath = `/api/event-subscriptions/${path.join('/')}`;
|
const targetPath = `/api/event-subscriptions/${path.join('/')}`;
|
||||||
@ -38,6 +38,9 @@ export async function GET(req: NextRequest, ctx: { params: Promise<{ path: strin
|
|||||||
export async function POST(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
|
export async function POST(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
|
||||||
return proxy(req, ctx);
|
return proxy(req, ctx);
|
||||||
}
|
}
|
||||||
|
export async function PATCH(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
|
||||||
|
return proxy(req, ctx);
|
||||||
|
}
|
||||||
export async function DELETE(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
|
export async function DELETE(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
|
||||||
return proxy(req, ctx);
|
return proxy(req, ctx);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Event Subscriptions base route — handles GET /api/event-subscriptions and POST /api/event-subscriptions.
|
||||||
|
* The [...path] catch-all only matches sub-paths like /api/event-subscriptions/:id.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
|
async function proxyBase(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
|
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
|
const qs = new URL(req.url).search;
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-request-id': req.headers.get('x-request-id') || crypto.randomUUID(),
|
||||||
|
'x-user-id': caller.id,
|
||||||
|
'x-product-id': req.headers.get('x-product-id') || process.env.PRODUCT_ID || 'lysnrai',
|
||||||
|
};
|
||||||
|
const fetchOptions: RequestInit = { method: req.method, headers };
|
||||||
|
if (req.method !== 'GET' && req.method !== 'HEAD') fetchOptions.body = await req.text();
|
||||||
|
const res = await fetch(`${PLATFORM_URL}/api/event-subscriptions${qs}`, fetchOptions);
|
||||||
|
const data = await res.json().catch(() => null);
|
||||||
|
return NextResponse.json(data ?? { error: res.statusText }, { status: res.status });
|
||||||
|
} catch (error) {
|
||||||
|
logError('Event subscriptions base proxy error', error);
|
||||||
|
return NextResponse.json({ error: 'Service unavailable' }, { status: 502 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
return proxyBase(req);
|
||||||
|
}
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
return proxyBase(req);
|
||||||
|
}
|
||||||
@ -3,14 +3,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
const { path } = await params;
|
const { path } = await params;
|
||||||
const targetPath = `/api/exports/${path.join('/')}`;
|
const targetPath = `/api/exports/${path.join('/')}`;
|
||||||
|
|||||||
@ -5,18 +5,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
const EXTRACTION_SERVICE_URL =
|
const EXTRACTION_SERVICE_URL = process.env.EXTRACTION_SERVICE_URL || 'http://localhost:4005';
|
||||||
process.env.EXTRACTION_SERVICE_URL || 'http://localhost:4005';
|
|
||||||
|
|
||||||
async function proxyToExtraction(
|
async function proxyToExtraction(
|
||||||
req: NextRequest,
|
req: NextRequest,
|
||||||
{ params }: { params: Promise<{ path: string[] }> },
|
{ params }: { params: Promise<{ path: string[] }> }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) {
|
if (!caller) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
}
|
}
|
||||||
@ -50,37 +49,22 @@ async function proxyToExtraction(
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError('Extraction proxy error', error);
|
logError('Extraction proxy error', error);
|
||||||
return NextResponse.json(
|
return NextResponse.json({ error: 'Extraction service unavailable' }, { status: 502 });
|
||||||
{ error: 'Extraction service unavailable' },
|
|
||||||
{ status: 502 },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(req: NextRequest, context: { params: Promise<{ path: string[] }> }) {
|
||||||
req: NextRequest,
|
|
||||||
context: { params: Promise<{ path: string[] }> },
|
|
||||||
) {
|
|
||||||
return proxyToExtraction(req, context);
|
return proxyToExtraction(req, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function POST(
|
export async function POST(req: NextRequest, context: { params: Promise<{ path: string[] }> }) {
|
||||||
req: NextRequest,
|
|
||||||
context: { params: Promise<{ path: string[] }> },
|
|
||||||
) {
|
|
||||||
return proxyToExtraction(req, context);
|
return proxyToExtraction(req, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function PUT(
|
export async function PUT(req: NextRequest, context: { params: Promise<{ path: string[] }> }) {
|
||||||
req: NextRequest,
|
|
||||||
context: { params: Promise<{ path: string[] }> },
|
|
||||||
) {
|
|
||||||
return proxyToExtraction(req, context);
|
return proxyToExtraction(req, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function DELETE(
|
export async function DELETE(req: NextRequest, context: { params: Promise<{ path: string[] }> }) {
|
||||||
req: NextRequest,
|
|
||||||
context: { params: Promise<{ path: string[] }> },
|
|
||||||
) {
|
|
||||||
return proxyToExtraction(req, context);
|
return proxyToExtraction(req, context);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,11 +5,11 @@
|
|||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import * as growthClient from '@/lib/growth-client';
|
import * as growthClient from '@/lib/growth-client';
|
||||||
export async function PATCH(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
export async function PATCH(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller || !['super_admin', 'admin'].includes(caller.role)) {
|
if (!caller || !['super_admin', 'admin'].includes(caller.role)) {
|
||||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||||
}
|
}
|
||||||
@ -40,7 +40,7 @@ export async function PATCH(req: NextRequest, { params }: { params: Promise<{ id
|
|||||||
}
|
}
|
||||||
export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller || caller.role !== 'super_admin') {
|
if (!caller || caller.role !== 'super_admin') {
|
||||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,11 +5,11 @@
|
|||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import * as growthClient from '@/lib/growth-client';
|
import * as growthClient from '@/lib/growth-client';
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller || !['super_admin', 'admin'].includes(caller.role)) {
|
if (!caller || !['super_admin', 'admin'].includes(caller.role)) {
|
||||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,12 +5,12 @@
|
|||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import * as growthClient from '@/lib/growth-client';
|
import * as growthClient from '@/lib/growth-client';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) {
|
if (!caller) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
}
|
}
|
||||||
@ -31,7 +31,7 @@ export async function GET(req: NextRequest) {
|
|||||||
}
|
}
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller || !['super_admin', 'admin'].includes(caller.role)) {
|
if (!caller || !['super_admin', 'admin'].includes(caller.role)) {
|
||||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,14 +3,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
const { path } = await params;
|
const { path } = await params;
|
||||||
const targetPath = `/api/ratelimit/ip-rules/${path.join('/')}`;
|
const targetPath = `/api/ratelimit/ip-rules/${path.join('/')}`;
|
||||||
|
|||||||
39
dashboards/admin-web/src/app/api/ip-rules/route.ts
Normal file
39
dashboards/admin-web/src/app/api/ip-rules/route.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* IP Rules base route — handles GET /api/ip-rules and POST /api/ip-rules.
|
||||||
|
* Maps to platform-service GET /api/ratelimit/ip-rules and POST /api/ratelimit/ip-rules.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
|
async function proxyBase(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
|
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
|
const qs = new URL(req.url).search;
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-request-id': req.headers.get('x-request-id') || crypto.randomUUID(),
|
||||||
|
'x-user-id': caller.id,
|
||||||
|
'x-product-id': req.headers.get('x-product-id') || process.env.PRODUCT_ID || 'lysnrai',
|
||||||
|
};
|
||||||
|
const fetchOptions: RequestInit = { method: req.method, headers };
|
||||||
|
if (req.method !== 'GET' && req.method !== 'HEAD') fetchOptions.body = await req.text();
|
||||||
|
const res = await fetch(`${PLATFORM_URL}/api/ratelimit/ip-rules${qs}`, fetchOptions);
|
||||||
|
const data = await res.json().catch(() => null);
|
||||||
|
return NextResponse.json(data ?? { error: res.statusText }, { status: res.status });
|
||||||
|
} catch (error) {
|
||||||
|
logError('IP rules base proxy error', error);
|
||||||
|
return NextResponse.json({ error: 'Service unavailable' }, { status: 502 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
return proxyBase(req);
|
||||||
|
}
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
return proxyBase(req);
|
||||||
|
}
|
||||||
@ -3,14 +3,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
const { path } = await params;
|
const { path } = await params;
|
||||||
const targetPath = `/api/jobs/${path.join('/')}`;
|
const targetPath = `/api/jobs/${path.join('/')}`;
|
||||||
|
|||||||
@ -3,14 +3,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
const { path } = await params;
|
const { path } = await params;
|
||||||
const targetPath = `/api/knowledge/${path.join('/')}`;
|
const targetPath = `/api/knowledge/${path.join('/')}`;
|
||||||
|
|||||||
@ -3,14 +3,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
const { path } = await params;
|
const { path } = await params;
|
||||||
const targetPath = `/api/settings/maintenance/${path.join('/')}`;
|
const targetPath = `/api/settings/maintenance/${path.join('/')}`;
|
||||||
|
|||||||
@ -3,14 +3,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
const { path } = await params;
|
const { path } = await params;
|
||||||
const targetPath = `/api/marketplace/${path.join('/')}`;
|
const targetPath = `/api/marketplace/${path.join('/')}`;
|
||||||
|
|||||||
@ -5,14 +5,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) {
|
if (!caller) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|||||||
39
dashboards/admin-web/src/app/api/orgs/route.ts
Normal file
39
dashboards/admin-web/src/app/api/orgs/route.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Orgs base route — handles GET /api/orgs and POST /api/orgs.
|
||||||
|
* Maps to platform-service GET /api/orgs and POST /api/orgs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
|
async function proxyBase(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
|
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
|
const qs = new URL(req.url).search;
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-request-id': req.headers.get('x-request-id') || crypto.randomUUID(),
|
||||||
|
'x-user-id': caller.id,
|
||||||
|
'x-product-id': req.headers.get('x-product-id') || process.env.PRODUCT_ID || 'lysnrai',
|
||||||
|
};
|
||||||
|
const fetchOptions: RequestInit = { method: req.method, headers };
|
||||||
|
if (req.method !== 'GET' && req.method !== 'HEAD') fetchOptions.body = await req.text();
|
||||||
|
const res = await fetch(`${PLATFORM_URL}/api/orgs${qs}`, fetchOptions);
|
||||||
|
const data = await res.json().catch(() => null);
|
||||||
|
return NextResponse.json(data ?? { error: res.statusText }, { status: res.status });
|
||||||
|
} catch (error) {
|
||||||
|
logError('Orgs base proxy error', error);
|
||||||
|
return NextResponse.json({ error: 'Service unavailable' }, { status: 502 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
return proxyBase(req);
|
||||||
|
}
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
return proxyBase(req);
|
||||||
|
}
|
||||||
@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { deactivatePromo } from '@/lib/growth-client';
|
import { deactivatePromo } from '@/lib/growth-client';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ type RouteContext = { params: Promise<{ id: string }> };
|
|||||||
|
|
||||||
export async function DELETE(req: NextRequest, ctx: RouteContext) {
|
export async function DELETE(req: NextRequest, ctx: RouteContext) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller || !['super_admin', 'admin'].includes(caller.role)) {
|
if (!caller || !['super_admin', 'admin'].includes(caller.role)) {
|
||||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||||
}
|
}
|
||||||
@ -29,7 +29,7 @@ export async function DELETE(req: NextRequest, ctx: RouteContext) {
|
|||||||
|
|
||||||
export async function PATCH(req: NextRequest, ctx: RouteContext) {
|
export async function PATCH(req: NextRequest, ctx: RouteContext) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller || !['super_admin', 'admin'].includes(caller.role)) {
|
if (!caller || !['super_admin', 'admin'].includes(caller.role)) {
|
||||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,11 +8,11 @@
|
|||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import * as growthClient from '@/lib/growth-client';
|
import * as growthClient from '@/lib/growth-client';
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) {
|
if (!caller) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
}
|
}
|
||||||
@ -27,7 +27,7 @@ export async function GET(req: NextRequest) {
|
|||||||
}
|
}
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller || !['super_admin', 'admin'].includes(caller.role)) {
|
if (!caller || !['super_admin', 'admin'].includes(caller.role)) {
|
||||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import * as growthClient from '@/lib/growth-client';
|
import * as growthClient from '@/lib/growth-client';
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) {
|
if (!caller) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,14 +3,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
const { path } = await params;
|
const { path } = await params;
|
||||||
const targetPath = `/api/reviews/${path.join('/')}`;
|
const targetPath = `/api/reviews/${path.join('/')}`;
|
||||||
|
|||||||
@ -3,14 +3,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
const { path } = await params;
|
const { path } = await params;
|
||||||
const targetPath = `/api/runs/${path.join('/')}`;
|
const targetPath = `/api/runs/${path.join('/')}`;
|
||||||
@ -38,3 +38,6 @@ export async function GET(req: NextRequest, ctx: { params: Promise<{ path: strin
|
|||||||
export async function POST(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
|
export async function POST(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
|
||||||
return proxy(req, ctx);
|
return proxy(req, ctx);
|
||||||
}
|
}
|
||||||
|
export async function PATCH(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
|
||||||
|
return proxy(req, ctx);
|
||||||
|
}
|
||||||
|
|||||||
39
dashboards/admin-web/src/app/api/runs/route.ts
Normal file
39
dashboards/admin-web/src/app/api/runs/route.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Runs base route — handles GET /api/runs and POST /api/runs.
|
||||||
|
* Maps to platform-service GET /api/runs and POST /api/runs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
|
async function proxyBase(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
|
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
|
const qs = new URL(req.url).search;
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-request-id': req.headers.get('x-request-id') || crypto.randomUUID(),
|
||||||
|
'x-user-id': caller.id,
|
||||||
|
'x-product-id': req.headers.get('x-product-id') || process.env.PRODUCT_ID || 'lysnrai',
|
||||||
|
};
|
||||||
|
const fetchOptions: RequestInit = { method: req.method, headers };
|
||||||
|
if (req.method !== 'GET' && req.method !== 'HEAD') fetchOptions.body = await req.text();
|
||||||
|
const res = await fetch(`${PLATFORM_URL}/api/runs${qs}`, fetchOptions);
|
||||||
|
const data = await res.json().catch(() => null);
|
||||||
|
return NextResponse.json(data ?? { error: res.statusText }, { status: res.status });
|
||||||
|
} catch (error) {
|
||||||
|
logError('Runs base proxy error', error);
|
||||||
|
return NextResponse.json({ error: 'Service unavailable' }, { status: 502 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
return proxyBase(req);
|
||||||
|
}
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
return proxyBase(req);
|
||||||
|
}
|
||||||
@ -3,14 +3,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
const { path } = await params;
|
const { path } = await params;
|
||||||
const targetPath = `/api/sessions/${path.join('/')}`;
|
const targetPath = `/api/sessions/${path.join('/')}`;
|
||||||
|
|||||||
39
dashboards/admin-web/src/app/api/sessions/route.ts
Normal file
39
dashboards/admin-web/src/app/api/sessions/route.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Sessions base route — handles GET /api/sessions and DELETE /api/sessions.
|
||||||
|
* Maps to platform-service GET /api/sessions and DELETE /api/sessions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
|
async function proxyBase(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
|
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
|
const qs = new URL(req.url).search;
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-request-id': req.headers.get('x-request-id') || crypto.randomUUID(),
|
||||||
|
'x-user-id': caller.id,
|
||||||
|
'x-product-id': req.headers.get('x-product-id') || process.env.PRODUCT_ID || 'lysnrai',
|
||||||
|
};
|
||||||
|
const fetchOptions: RequestInit = { method: req.method, headers };
|
||||||
|
if (req.method !== 'GET' && req.method !== 'HEAD') fetchOptions.body = await req.text();
|
||||||
|
const res = await fetch(`${PLATFORM_URL}/api/sessions${qs}`, fetchOptions);
|
||||||
|
const data = await res.json().catch(() => null);
|
||||||
|
return NextResponse.json(data ?? { error: res.statusText }, { status: res.status });
|
||||||
|
} catch (error) {
|
||||||
|
logError('Sessions base proxy error', error);
|
||||||
|
return NextResponse.json({ error: 'Service unavailable' }, { status: 502 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
return proxyBase(req);
|
||||||
|
}
|
||||||
|
export async function DELETE(req: NextRequest) {
|
||||||
|
return proxyBase(req);
|
||||||
|
}
|
||||||
@ -5,14 +5,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) {
|
if (!caller) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import * as billingClient from '@/lib/billing-client';
|
import * as billingClient from '@/lib/billing-client';
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) {
|
if (!caller) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,14 +5,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) {
|
if (!caller) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,14 +3,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getCurrentUser } from '@/lib/auth-server';
|
import { getCurrentUserFromRequest } from '@/lib/auth-server';
|
||||||
import { logError } from '@/lib/logger';
|
import { logError } from '@/lib/logger';
|
||||||
|
|
||||||
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003';
|
||||||
|
|
||||||
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
||||||
try {
|
try {
|
||||||
const caller = await getCurrentUser(req.headers.get('authorization'));
|
const caller = await getCurrentUserFromRequest(req);
|
||||||
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
const { path } = await params;
|
const { path } = await params;
|
||||||
const targetPath = `/api/webhooks/${path.join('/')}`;
|
const targetPath = `/api/webhooks/${path.join('/')}`;
|
||||||
|
|||||||
@ -42,6 +42,25 @@ export async function getCurrentUser(authHeader: string | null): Promise<UserDoc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current user from a Request object.
|
||||||
|
* Checks cookies first (Next.js httpOnly cookie), then Authorization header.
|
||||||
|
* Returns null if not authenticated.
|
||||||
|
*/
|
||||||
|
export async function getCurrentUserFromRequest(request: Request): Promise<UserDoc | null> {
|
||||||
|
const cookieHeader = request.headers.get('cookie') || '';
|
||||||
|
const tokenMatch = cookieHeader.match(/(?:^|;\s*)token=([^;]+)/);
|
||||||
|
const authHeader = request.headers.get('authorization');
|
||||||
|
|
||||||
|
if (tokenMatch) {
|
||||||
|
return getCurrentUser(`Bearer ${tokenMatch[1]}`);
|
||||||
|
}
|
||||||
|
if (authHeader) {
|
||||||
|
return getCurrentUser(authHeader);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Require an admin user from the request. Checks cookies first, then Authorization header.
|
* Require an admin user from the request. Checks cookies first, then Authorization header.
|
||||||
* Throws Error("Unauthorized") if not authenticated or not admin/super_admin role.
|
* Throws Error("Unauthorized") if not authenticated or not admin/super_admin role.
|
||||||
|
|||||||
37
dashboards/admin-web/src/lib/proxy-fetch.ts
Normal file
37
dashboards/admin-web/src/lib/proxy-fetch.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Shared fetch helper for admin-web pages that call proxy API routes.
|
||||||
|
* Automatically injects Authorization + x-product-id headers from localStorage.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function getAuthHeaders(): Record<string, string> {
|
||||||
|
if (typeof window === 'undefined') return {};
|
||||||
|
const headers: Record<string, string> = {};
|
||||||
|
const token = localStorage.getItem('admin_access_token');
|
||||||
|
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||||
|
const productId = localStorage.getItem('admin_selected_product');
|
||||||
|
if (productId) headers['x-product-id'] = productId;
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a scoped fetch helper for a given proxy base path.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* const apiFetch = createProxyFetch('/api/jobs');
|
||||||
|
* const data = await apiFetch('list'); // GET /api/jobs/list
|
||||||
|
* await apiFetch('123', { method: 'PUT', body: ... });
|
||||||
|
*/
|
||||||
|
export function createProxyFetch(basePath: string) {
|
||||||
|
return async function proxyFetch(path: string, opts?: RequestInit) {
|
||||||
|
const url = !path || path.startsWith('?') ? `${basePath}${path}` : `${basePath}/${path}`;
|
||||||
|
const res = await fetch(url, {
|
||||||
|
...opts,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...getAuthHeaders(),
|
||||||
|
...opts?.headers,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return res.json();
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user