From d0cb3a22389f32d899715858e2f866895dec9a9c Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Sun, 1 Mar 2026 16:39:38 -0800 Subject: [PATCH] =?UTF-8?q?feat(marketplace):=20purchase=20repository=20?= =?UTF-8?q?=E2=80=94=20build/complete/refund=20docs,=2070/30=20revenue=20s?= =?UTF-8?q?hare,=20author=20earnings=20aggregation=20(15=20tests)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WINDSURF/.last-refresh.log | 12 +- .../AZURE_CONNECTION_AUDIT.md | 186 ++++++++++++++++++ .../mobile-code-quality.md | 74 +++++++ .../production-readiness.md | 82 ++++++++ .../repo_backup-and-push.md | 36 ++++ .../repo_backup-main-branch.md | 31 +++ .../repo_commit-workspace.md | 44 +++++ .../learning_ai_peakpulse/repo_push-repos.md | 30 +++ .../learning_ai_peakpulse/repo_sync-repos.md | 27 +++ .../marketplace/purchase-repository.test.ts | 174 ++++++++++++++++ .../marketplace/purchase-repository.ts | 133 +++++++++++++ 11 files changed, 823 insertions(+), 6 deletions(-) create mode 100644 __LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_docs/learning_ai_common_plat/AZURE_CONNECTION_AUDIT.md create mode 100644 __LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/mobile-code-quality.md create mode 100644 __LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/production-readiness.md create mode 100644 __LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/repo_backup-and-push.md create mode 100644 __LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/repo_backup-main-branch.md create mode 100644 __LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/repo_commit-workspace.md create mode 100644 __LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/repo_push-repos.md create mode 100644 __LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/repo_sync-repos.md create mode 100644 services/platform-service/src/modules/marketplace/purchase-repository.test.ts create mode 100644 services/platform-service/src/modules/marketplace/purchase-repository.ts diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/.last-refresh.log b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/.last-refresh.log index 37efdd35..ee2157b2 100644 --- a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/.last-refresh.log +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/.last-refresh.log @@ -1,9 +1,9 @@ -Last refresh: 2026-03-01T07:00:13Z (2026-02-28 23:00:13 PST) +Last refresh: 2026-03-02T00:28:03Z (2026-03-01 16:28:03 PST) Cascade conversations: 50 (495M) -Memories: 56 +Memories: 59 Implicit context: 20 -Code tracker dirs: 182 -File edit history: 2010 entries +Code tracker dirs: 230 +File edit history: 2075 entries Workspace storage: 28 workspaces -Repo docs: 14 files across 3 repos -Repo workflows: 28 files across 5 repos +Repo docs: 15 files across 3 repos +Repo workflows: 35 files across 6 repos diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_docs/learning_ai_common_plat/AZURE_CONNECTION_AUDIT.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_docs/learning_ai_common_plat/AZURE_CONNECTION_AUDIT.md new file mode 100644 index 00000000..344e3a15 --- /dev/null +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_docs/learning_ai_common_plat/AZURE_CONNECTION_AUDIT.md @@ -0,0 +1,186 @@ +# Azure Connection Audit — Full Workspace Report + +> **Date:** 2026-02-22 +> **Scope:** `learning_ai_common_plat`, `learning_voice_ai_agent`, `learning_multimodal_memory_agents`, `learning_ai_clock`, `learning_ai_fastgap` +> **Auditor:** Cascade (AI) + +--- + +## Executive Summary + +| Category | Issues Found | Fixed (session 1) | Fixed (session 2) | Remaining | +| ---------------------- | ------------ | ----------------- | ----------------------------------------- | ------------------- | +| `x-request-id` missing | 12 clients | 2 (MindLyst) | **9** (root cause + feature-flags) | 0 ✅ | +| `x-product-id` missing | 6 clients | 0 | **6** (admin + user dashboards + Python) | 0 ✅ | +| Cosmos PK mismatch | 1 container | 0 (flagged) | 0 | 1 (needs migration) | +| `.env.example` gaps | 4 files | 1 (MindLyst) | **3** (ChronoMind, user-dash, admin-dash) | 0 ✅ | +| Hardcoded productId | 2 instances | 0 | **2** (telemetry.ts, platform_client.py) | 0 ✅ | +| Python client gaps | 1 file | 0 | **1** (headers + config) | 0 ✅ | + +--- + +## 1. `x-request-id` Header — Root Cause + +### Finding + +**`@bytelyst/api-client` does NOT auto-inject `x-request-id`.** + +The `createApiClient()` factory in `packages/api-client/src/client.ts` only sets `Content-Type`, auth token (via `getToken`), and caller-supplied `defaultHeaders`. No `x-request-id` is generated. This means **every consumer** that relies on `@bytelyst/api-client` without explicitly adding the header is missing request tracing. + +### Root Cause Fix + +Add `x-request-id: crypto.randomUUID()` to `buildHeaders()` in `packages/api-client/src/client.ts`. This single change propagates to all consumers automatically. + +### Affected Clients (missing `x-request-id`) + +| Repo | File | Client Pattern | +| ---------------- | -------------------------------------------------- | ------------------------------------- | +| `common_plat` | `dashboards/admin-web/src/lib/billing-client.ts` | `createApiClient` — no `x-request-id` | +| `common_plat` | `dashboards/admin-web/src/lib/growth-client.ts` | `createApiClient` — no `x-request-id` | +| `common_plat` | `dashboards/admin-web/src/lib/platform-client.ts` | `createApiClient` — no `x-request-id` | +| `common_plat` | `dashboards/tracker-web/src/lib/tracker-client.ts` | `createApiClient` — no `x-request-id` | +| `common_plat` | `packages/extraction/src/client.ts` | `createApiClient` — no `x-request-id` | +| `voice_ai_agent` | `user-dashboard-web/src/lib/billing-client.ts` | `createApiClient` — no `x-request-id` | +| `voice_ai_agent` | `user-dashboard-web/src/lib/growth-client.ts` | `createApiClient` — no `x-request-id` | +| `voice_ai_agent` | `user-dashboard-web/src/lib/platform-client.ts` | `createApiClient` — no `x-request-id` | +| `voice_ai_agent` | `user-dashboard-web/src/lib/feature-flags.ts` | Custom `fetch` — no `x-request-id` | +| `voice_ai_agent` | `backend/src/clients/platform_client.py` | `httpx` — no `x-request-id` | + +### Already Fixed (previous session) + +| Repo | File | Status | +| ------------------- | ------------------------------- | ----------------------------- | +| `multimodal_memory` | `web/src/lib/billing-client.ts` | ✅ Added via `defaultHeaders` | +| `multimodal_memory` | `web/src/lib/feature-flags.ts` | ✅ Added manually | + +### Already Correct + +| Repo | File | Status | +| ----------------------- | ------------------------------------------ | ------------------------------------------- | +| `ai_fastgap` (NomGap) | `src/api/client.ts` | ✅ Custom client with `crypto.randomUUID()` | +| `ai_clock` (ChronoMind) | `web/src/lib/platform-sync.ts` | ✅ Custom client with `crypto.randomUUID()` | +| `voice_ai_agent` | `backend/src/main.py` | ✅ Middleware propagates/generates | +| `voice_ai_agent` | `backend/src/clients/extraction_client.py` | ✅ Passes `request_id` param | + +--- + +## 2. `x-product-id` Header Gaps + +### Clients Missing `x-product-id` + +| Repo | File | Impact | +| ---------------- | ----------------------------------------------- | --------------------------------- | +| `common_plat` | `admin-web/src/lib/billing-client.ts` | Server can't filter by product | +| `common_plat` | `admin-web/src/lib/growth-client.ts` | Server can't filter by product | +| `voice_ai_agent` | `user-dashboard-web/src/lib/billing-client.ts` | Server can't filter by product | +| `voice_ai_agent` | `user-dashboard-web/src/lib/growth-client.ts` | Server can't filter by product | +| `voice_ai_agent` | `user-dashboard-web/src/lib/platform-client.ts` | Passes in body, not header | +| `voice_ai_agent` | `backend/src/clients/platform_client.py` | Passes in body/params, not header | + +### Already Correct + +| Repo | File | +| ------------------------------ | ------------------------------------------------------------- | +| `ai_fastgap` (NomGap) | `src/api/client.ts` — `x-product-id: API_CONFIG.productId` | +| `ai_clock` (ChronoMind) | `web/src/lib/platform-sync.ts` — `x-product-id` header | +| `multimodal_memory` (MindLyst) | `web/src/lib/billing-client.ts` — via `defaultHeaders` | +| `multimodal_memory` (MindLyst) | `web/src/lib/feature-flags.ts` — explicit header | +| `common_plat` | `tracker-web/src/lib/tracker-client.ts` — from `localStorage` | + +--- + +## 3. Cosmos DB Partition Key Mismatch + +### `referrals` Container — 3-way Mismatch + +| Location | Partition Key | +| ----------------------------------------------------- | ------------- | +| `platform-service/src/lib/cosmos-init.ts` | `/id` | +| MindLyst `web/src/lib/cosmos.ts` | `/userId` | +| Admin dashboard `admin-web/src/lib/cosmos.ts` | `/referrerId` | +| User dashboard `user-dashboard-web/src/lib/cosmos.ts` | `/referrerId` | + +**Status:** Flagged in previous session. Cannot be fixed without data migration. Comment added to `cosmos-init.ts`. + +**Risk:** Cross-partition queries will silently succeed but may return incomplete results or fail on point reads if the wrong partition key is specified. + +--- + +## 4. Missing Environment Variables in `.env.example` Files + +### ChronoMind `web/.env.example` + +Currently only has: + +``` +NEXT_PUBLIC_PLATFORM_SERVICE_URL=http://localhost:4003/api +``` + +**Missing:** + +- `NEXT_PUBLIC_PRODUCT_ID=chronomind` — used implicitly by `platform-sync.ts` (hardcoded there, but should be env-driven for consistency) + +### LysnrAI `user-dashboard-web/.env.example` + +**Missing:** + +- `NEXT_PUBLIC_PRODUCT_ID=lysnrai` — referenced by `feature-flags.ts` line 10 +- `NEXT_PUBLIC_PLATFORM_SERVICE_URL=http://localhost:4003` — referenced by `feature-flags.ts` line 11 + +Has `PLATFORM_SERVICE_URL` (server-side) but not the `NEXT_PUBLIC_` variant (client-side). + +### LysnrAI root `.env.example` + +**Missing:** + +- `NEXT_PUBLIC_PRODUCT_ID` — not needed at root level (desktop app), so this is informational only. + +### Admin dashboard `.env.example` + +**Missing:** + +- `AZURE_KEYVAULT_URL` — referenced by `instrumentation.ts` but not in `.env.example` + +--- + +## 5. Hardcoded `productId` Values + +| Repo | File | Line | Value | Should Use | +| ------------------- | ---------------------------------------- | ------- | ----------------------------- | ------------------------------------ | +| `multimodal_memory` | `web/src/lib/telemetry.ts` | 19 | `productId: 'mindlyst'` | `process.env.NEXT_PUBLIC_PRODUCT_ID` | +| `voice_ai_agent` | `backend/src/clients/platform_client.py` | 86, 101 | `product_id: str = "lysnrai"` | `settings.PRODUCT_ID` or config | + +--- + +## 6. Python Backend Client Gaps (`platform_client.py`) + +The `PlatformClient` class in `backend/src/clients/platform_client.py` has several issues: + +1. **No `x-request-id` header** on any request +2. **No `x-product-id` header** on any request +3. **Creates new `httpx.AsyncClient` per request** — no connection pooling +4. **Hardcoded `product_id="lysnrai"` defaults** — should use config + +--- + +## 7. Previously Fixed (Session 1) + +| Fix | Repo | File | +| ------------------------------------------- | ------------------- | -------------------------------------------------- | +| Added `x-request-id` to billing client | `multimodal_memory` | `web/src/lib/billing-client.ts` | +| Added `x-request-id` to feature flags | `multimodal_memory` | `web/src/lib/feature-flags.ts` | +| Added 13 MindLyst containers to cosmos-init | `common_plat` | `services/platform-service/src/lib/cosmos-init.ts` | +| Added Blob Storage creds to Python config | `voice_ai_agent` | `backend/src/config.py` | +| Added missing env vars to MindLyst | `multimodal_memory` | `web/.env.example` | + +--- + +## 8. Recommended Fix Order + +1. **P0 — Root cause:** Add `x-request-id` auto-generation to `@bytelyst/api-client` `buildHeaders()` → fixes 9 TS clients at once +2. **P0 — LysnrAI feature-flags:** Add `x-request-id` to the custom `fetch` call in `user-dashboard-web/src/lib/feature-flags.ts` +3. **P1 — Python backend:** Add `x-request-id` and `x-product-id` headers to `platform_client.py` +4. **P1 — Env vars:** Add missing `NEXT_PUBLIC_*` vars to ChronoMind, LysnrAI user-dashboard, admin-dashboard `.env.example` files +5. **P2 — `x-product-id`:** Add to admin/user dashboard clients via `defaultHeaders` in `createApiClient` config +6. **P2 — Hardcoded productId:** Replace in `telemetry.ts` and `platform_client.py` +7. **P3 — Referrals PK mismatch:** Requires data migration strategy (separate task) diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/mobile-code-quality.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/mobile-code-quality.md new file mode 100644 index 00000000..b42c877d --- /dev/null +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/mobile-code-quality.md @@ -0,0 +1,74 @@ +--- +description: Verify iOS/mobile code compiles, all files are in Xcode targets, and passes quality checks +--- + +# Mobile Code Quality — PeakPulse + +## Steps + +1. Ensure working tree is clean: + // turbo + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git status --short +``` + +2. Generate Xcode project from project.yml: + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse/ios && xcodegen generate +``` + +3. Build PeakPulse target (iOS Simulator): + +```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 +``` + +4. Build PeakPulseTests target: + +```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 +``` + +5. Check for print() statements (should use os.Logger instead): + // turbo + +```bash +grep -rn "print(" /Users/sd9235/code/mygh/learning_ai_peakpulse/ios/PeakPulse/ --include="*.swift" | grep -v "// print" | grep -v "/// " || echo "No print() found — OK" +``` + +6. Check for force unwraps: + // turbo + +```bash +grep -rn '![^=]' /Users/sd9235/code/mygh/learning_ai_peakpulse/ios/PeakPulse/ --include="*.swift" | grep -v 'IBOutlet' | grep -v '// !' | grep -v '!=' | grep -v '!//' | head -20 || echo "No force unwraps found — OK" +``` + +7. Verify all Swift files are under ios/: + // turbo + +```bash +find /Users/sd9235/code/mygh/learning_ai_peakpulse/ios -name "*.swift" | wc -l +``` + +8. Check entitlements file has required keys: + // turbo + +```bash +cat /Users/sd9235/code/mygh/learning_ai_peakpulse/ios/PeakPulse/PeakPulse.entitlements +``` + +## Key Files + +- `ios/project.yml` — XcodeGen project spec +- `ios/PeakPulse/PeakPulse.entitlements` — App entitlements +- `ios/PeakPulse/Theme/PeakPulseTheme.swift` — Design tokens + +## Troubleshooting + +| Issue | Fix | +| ----------------------------- | -------------------------------------------------------------------------- | +| ByteLystPlatformSDK not found | Ensure `../../learning_ai_common_plat/packages/swift-platform-sdk/` exists | +| xcodegen not installed | `brew install xcodegen` | +| Build fails on Simulator | Check deployment target is iOS 17.0 | diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/production-readiness.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/production-readiness.md new file mode 100644 index 00000000..321c4e23 --- /dev/null +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/production-readiness.md @@ -0,0 +1,82 @@ +--- +description: Production readiness check — run all checks, fix as you go, commit incrementally +--- + +# Production Readiness — PeakPulse + +## Steps + +1. Check git status is clean: + // turbo + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git status --short +``` + +2. Verify project.yml generates successfully: + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse/ios && xcodegen generate +``` + +3. Build all iOS targets: + +```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 +``` + +4. Run unit tests: + +```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 +``` + +5. Verify no print() statements in production code: + // turbo + +```bash +grep -rn "print(" /Users/sd9235/code/mygh/learning_ai_peakpulse/ios/PeakPulse/ --include="*.swift" | grep -v "// " || echo "PASS: No print() found" +``` + +6. Verify no hardcoded colors (should use PeakPulseColors.\*): + // turbo + +```bash +grep -rn "Color(" /Users/sd9235/code/mygh/learning_ai_peakpulse/ios/PeakPulse/Views/ --include="*.swift" | grep -v "PeakPulseColors" | grep -v Theme | head -10 || echo "PASS: No hardcoded colors" +``` + +7. Verify entitlements are correct: + // turbo + +```bash +cat /Users/sd9235/code/mygh/learning_ai_peakpulse/ios/PeakPulse/PeakPulse.entitlements +``` + +8. Verify .env.example has all required keys: + // turbo + +```bash +cat /Users/sd9235/code/mygh/learning_ai_peakpulse/.env.example +``` + +9. Run platform-service peak-sessions tests: + +```bash +cd /Users/sd9235/code/mygh/learning_ai_common_plat && npx vitest run services/platform-service/src/modules/peak-sessions/peak-sessions.test.ts +``` + +10. Verify CI workflow exists and is valid: + // turbo + +```bash +cat /Users/sd9235/code/mygh/learning_ai_peakpulse/.github/workflows/ci.yml | head -20 +``` + +11. Check file counts match docs: + // turbo + +```bash +echo "Swift files:" && find /Users/sd9235/code/mygh/learning_ai_peakpulse/ios -name "*.swift" | wc -l && echo "Test files:" && find /Users/sd9235/code/mygh/learning_ai_peakpulse/ios/PeakPulseTests -name "*.swift" | wc -l +``` + +12. Final commit and push if any fixes were made. diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/repo_backup-and-push.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/repo_backup-and-push.md new file mode 100644 index 00000000..5f1f9a8c --- /dev/null +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/repo_backup-and-push.md @@ -0,0 +1,36 @@ +--- +description: Backup main branch then push PeakPulse repo to origin +--- + +1. Check for uncommitted changes + // turbo + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git status --short +``` + +2. If there are changes, commit them with an appropriate message + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git add -A && git commit -m "chore: save work in progress" +``` + +3. Create a timestamped backup branch + // turbo + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git branch backup/main-$(date +%Y%m%d-%H%M%S) +``` + +4. Push main to origin + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git push origin main +``` + +5. Verify push succeeded + // turbo + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git log --oneline -3 +``` diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/repo_backup-main-branch.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/repo_backup-main-branch.md new file mode 100644 index 00000000..4bc0dcf0 --- /dev/null +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/repo_backup-main-branch.md @@ -0,0 +1,31 @@ +--- +description: Smart backup of main branches with duplicate detection +--- + +1. List existing backup branches + // turbo + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git branch --list 'backup/*' | tail -5 +``` + +2. Get current HEAD commit + // turbo + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git rev-parse --short HEAD +``` + +3. Create backup branch with timestamp (skip if HEAD hasn't changed since last backup) + // turbo + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git branch backup/main-$(date +%Y%m%d-%H%M%S) +``` + +4. Confirm backup created + // turbo + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git branch --list 'backup/*' | tail -3 +``` diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/repo_commit-workspace.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/repo_commit-workspace.md new file mode 100644 index 00000000..22d3a5d8 --- /dev/null +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/repo_commit-workspace.md @@ -0,0 +1,44 @@ +--- +description: Commit all workspace changes in logical order with intelligent messages +--- + +# Commit Workspace Changes — PeakPulse + +## Steps + +1. Check for uncommitted changes: + // turbo + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git status --short +``` + +2. If there are changes, stage all files: + // turbo + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git add -A +``` + +3. Review staged changes to craft an intelligent commit message: + // turbo + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git diff --cached --stat +``` + +4. Commit with a descriptive single-line message following the convention `type(scope): description`: + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git commit -m "(): " +``` + +**Commit types:** feat, fix, docs, refactor, test, chore +**Common scopes:** models, services, views, components, platform, tests, config, docs + +5. Verify commit: + // turbo + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git log -1 --oneline +``` diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/repo_push-repos.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/repo_push-repos.md new file mode 100644 index 00000000..68ae2a43 --- /dev/null +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/repo_push-repos.md @@ -0,0 +1,30 @@ +--- +description: Push local main branch to origin for PeakPulse repo +--- + +1. Check current branch is main + // turbo + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git branch --show-current +``` + +2. Check for uncommitted changes + // turbo + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git status --short +``` + +3. Push main to origin + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git push origin main +``` + +4. Verify + // turbo + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git log --oneline -1 +``` diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/repo_sync-repos.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/repo_sync-repos.md new file mode 100644 index 00000000..1f840acd --- /dev/null +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/repo_sync-repos.md @@ -0,0 +1,27 @@ +--- +description: Pull latest from origin main for PeakPulse repo +--- + +# Sync Repos — PeakPulse + +## Steps + +1. Ensure working tree is clean: + // turbo + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git status --short +``` + +2. Pull latest from origin main: + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git pull origin main +``` + +3. Verify current state: + // turbo + +```bash +cd /Users/sd9235/code/mygh/learning_ai_peakpulse && git log -3 --oneline +``` diff --git a/services/platform-service/src/modules/marketplace/purchase-repository.test.ts b/services/platform-service/src/modules/marketplace/purchase-repository.test.ts new file mode 100644 index 00000000..e1507060 --- /dev/null +++ b/services/platform-service/src/modules/marketplace/purchase-repository.test.ts @@ -0,0 +1,174 @@ +import { describe, it, expect } from 'vitest'; +import { + buildPurchaseDoc, + completePurchase, + refundPurchase, + aggregateAuthorEarnings, + type PurchaseDoc, +} from './purchase-repository.js'; + +describe('buildPurchaseDoc', () => { + const base = { + productId: 'jarvisjr', + userId: 'user_1', + listingId: 'listing_1', + listingTitle: 'Interview Coach Pro', + authorId: 'author_1', + priceUsd: 4.99, + }; + + it('creates a purchase with unique id', () => { + const a = buildPurchaseDoc(base); + const b = buildPurchaseDoc(base); + expect(a.id).not.toBe(b.id); + expect(a.id).toMatch(/^purchase_/); + }); + + it('sets status to pending', () => { + const doc = buildPurchaseDoc(base); + expect(doc.status).toBe('pending'); + }); + + it('calculates 70/30 revenue share', () => { + const doc = buildPurchaseDoc(base); + expect(doc.authorShareUsd).toBeCloseTo(3.49, 2); + expect(doc.platformShareUsd).toBeCloseTo(1.5, 2); + }); + + it('revenue shares sum to price', () => { + const doc = buildPurchaseDoc(base); + expect(doc.authorShareUsd + doc.platformShareUsd).toBeCloseTo(base.priceUsd, 1); + }); + + it('stores stripe session id if provided', () => { + const doc = buildPurchaseDoc({ ...base, stripeSessionId: 'cs_test_123' }); + expect(doc.stripeSessionId).toBe('cs_test_123'); + }); + + it('defaults stripe session to null', () => { + const doc = buildPurchaseDoc(base); + expect(doc.stripeSessionId).toBeNull(); + }); + + it('sets completedAt and refundedAt to null', () => { + const doc = buildPurchaseDoc(base); + expect(doc.completedAt).toBeNull(); + expect(doc.refundedAt).toBeNull(); + }); +}); + +describe('completePurchase', () => { + it('marks purchase as completed', () => { + const doc = buildPurchaseDoc({ + productId: 'jarvisjr', + userId: 'u1', + listingId: 'l1', + listingTitle: 'Test', + authorId: 'a1', + priceUsd: 9.99, + }); + const completed = completePurchase(doc, 'pi_test_456'); + expect(completed.status).toBe('completed'); + expect(completed.stripePaymentIntentId).toBe('pi_test_456'); + expect(completed.completedAt).toBeTruthy(); + }); + + it('preserves original fields', () => { + const doc = buildPurchaseDoc({ + productId: 'jarvisjr', + userId: 'u1', + listingId: 'l1', + listingTitle: 'Test', + authorId: 'a1', + priceUsd: 4.99, + }); + const completed = completePurchase(doc, 'pi_test'); + expect(completed.id).toBe(doc.id); + expect(completed.priceUsd).toBe(doc.priceUsd); + expect(completed.authorShareUsd).toBe(doc.authorShareUsd); + }); +}); + +describe('refundPurchase', () => { + it('marks purchase as refunded with reason', () => { + const doc = buildPurchaseDoc({ + productId: 'jarvisjr', + userId: 'u1', + listingId: 'l1', + listingTitle: 'Test', + authorId: 'a1', + priceUsd: 4.99, + }); + const completed = completePurchase(doc, 'pi_test'); + const refunded = refundPurchase(completed, 'Customer requested refund'); + expect(refunded.status).toBe('refunded'); + expect(refunded.refundReason).toBe('Customer requested refund'); + expect(refunded.refundedAt).toBeTruthy(); + }); +}); + +describe('aggregateAuthorEarnings', () => { + function makePurchase(overrides: Partial = {}): PurchaseDoc { + return { + id: `purchase_${Math.random()}`, + productId: 'jarvisjr', + userId: 'user_1', + listingId: 'listing_1', + listingTitle: 'Interview Coach', + authorId: 'author_1', + priceUsd: 4.99, + authorShareUsd: 3.49, + platformShareUsd: 1.5, + stripeSessionId: null, + stripePaymentIntentId: 'pi_test', + status: 'completed', + refundReason: null, + createdAt: new Date().toISOString(), + completedAt: new Date().toISOString(), + refundedAt: null, + ...overrides, + }; + } + + it('returns zero for no purchases', () => { + const result = aggregateAuthorEarnings([], 'author_1'); + expect(result.totalEarningsUsd).toBe(0); + expect(result.totalSales).toBe(0); + expect(result.listings).toHaveLength(0); + }); + + it('aggregates single listing sales', () => { + const purchases = [makePurchase(), makePurchase(), makePurchase()]; + const result = aggregateAuthorEarnings(purchases, 'author_1'); + expect(result.totalSales).toBe(3); + expect(result.totalEarningsUsd).toBeCloseTo(10.47, 2); + expect(result.listings).toHaveLength(1); + expect(result.listings[0].sales).toBe(3); + }); + + it('aggregates multiple listing sales', () => { + const purchases = [ + makePurchase({ listingId: 'listing_1', listingTitle: 'Coach A', authorShareUsd: 3.49 }), + makePurchase({ listingId: 'listing_2', listingTitle: 'Coach B', authorShareUsd: 6.99 }), + makePurchase({ listingId: 'listing_1', listingTitle: 'Coach A', authorShareUsd: 3.49 }), + ]; + const result = aggregateAuthorEarnings(purchases, 'author_1'); + expect(result.totalSales).toBe(3); + expect(result.listings).toHaveLength(2); + }); + + it('excludes refunded purchases', () => { + const purchases = [makePurchase(), makePurchase({ status: 'refunded' })]; + const result = aggregateAuthorEarnings(purchases, 'author_1'); + expect(result.totalSales).toBe(1); + }); + + it('excludes other authors', () => { + const purchases = [ + makePurchase({ authorId: 'author_1' }), + makePurchase({ authorId: 'author_2' }), + ]; + const result = aggregateAuthorEarnings(purchases, 'author_1'); + expect(result.totalSales).toBe(1); + }); +}); diff --git a/services/platform-service/src/modules/marketplace/purchase-repository.ts b/services/platform-service/src/modules/marketplace/purchase-repository.ts new file mode 100644 index 00000000..85e2369a --- /dev/null +++ b/services/platform-service/src/modules/marketplace/purchase-repository.ts @@ -0,0 +1,133 @@ +/** + * Purchase Repository — manages marketplace purchase records. + * Tracks who bought what, revenue share, and refund status. + */ + +import crypto from 'node:crypto'; + +export interface PurchaseDoc { + id: string; + productId: string; + userId: string; + listingId: string; + listingTitle: string; + authorId: string; + priceUsd: number; + authorShareUsd: number; + platformShareUsd: number; + stripeSessionId: string | null; + stripePaymentIntentId: string | null; + status: 'pending' | 'completed' | 'refunded' | 'failed'; + refundReason: string | null; + createdAt: string; + completedAt: string | null; + refundedAt: string | null; +} + +// Revenue share: 70% author, 30% platform +const AUTHOR_SHARE = 0.7; +const PLATFORM_SHARE = 0.3; + +export function buildPurchaseDoc(input: { + productId: string; + userId: string; + listingId: string; + listingTitle: string; + authorId: string; + priceUsd: number; + stripeSessionId?: string; +}): PurchaseDoc { + const now = new Date().toISOString(); + const authorShare = Math.round(input.priceUsd * AUTHOR_SHARE * 100) / 100; + const platformShare = Math.round(input.priceUsd * PLATFORM_SHARE * 100) / 100; + + return { + id: `purchase_${crypto.randomUUID()}`, + productId: input.productId, + userId: input.userId, + listingId: input.listingId, + listingTitle: input.listingTitle, + authorId: input.authorId, + priceUsd: input.priceUsd, + authorShareUsd: authorShare, + platformShareUsd: platformShare, + stripeSessionId: input.stripeSessionId ?? null, + stripePaymentIntentId: null, + status: 'pending', + refundReason: null, + createdAt: now, + completedAt: null, + refundedAt: null, + }; +} + +export function completePurchase(doc: PurchaseDoc, paymentIntentId: string): PurchaseDoc { + return { + ...doc, + status: 'completed', + stripePaymentIntentId: paymentIntentId, + completedAt: new Date().toISOString(), + }; +} + +export function refundPurchase(doc: PurchaseDoc, reason: string): PurchaseDoc { + return { + ...doc, + status: 'refunded', + refundReason: reason, + refundedAt: new Date().toISOString(), + }; +} + +// ── Author Earnings Aggregation ───────────────────────────── + +export interface AuthorEarnings { + authorId: string; + totalEarningsUsd: number; + totalSales: number; + pendingPayoutUsd: number; + listings: Array<{ + listingId: string; + listingTitle: string; + sales: number; + earningsUsd: number; + }>; +} + +export function aggregateAuthorEarnings( + purchases: PurchaseDoc[], + authorId: string +): AuthorEarnings { + const completed = purchases.filter(p => p.authorId === authorId && p.status === 'completed'); + + const byListing = new Map(); + let totalEarnings = 0; + + for (const p of completed) { + totalEarnings += p.authorShareUsd; + const existing = byListing.get(p.listingId); + if (existing) { + existing.sales += 1; + existing.earnings += p.authorShareUsd; + } else { + byListing.set(p.listingId, { + title: p.listingTitle, + sales: 1, + earnings: p.authorShareUsd, + }); + } + } + + return { + authorId, + totalEarningsUsd: Math.round(totalEarnings * 100) / 100, + totalSales: completed.length, + pendingPayoutUsd: Math.round(totalEarnings * 100) / 100, // Stub: all earnings are pending + listings: Array.from(byListing.entries()).map(([listingId, data]) => ({ + listingId, + listingTitle: data.title, + sales: data.sales, + earningsUsd: Math.round(data.earnings * 100) / 100, + })), + }; +}