From 69f5082e5ee92e5914fa07762d76b3628da4df30 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 12 Apr 2026 11:01:09 +0000 Subject: [PATCH] chore(dealix): launch runbooks, staging env templates, SQLite init on startup, product journey test Co-authored-by: VoXc2 --- salesflow-saas/.env.staging.example | 36 +++++++++++ salesflow-saas/README.md | 4 +- salesflow-saas/backend/app/database.py | 2 + salesflow-saas/backend/app/main.py | 3 + .../test_launch_staging_product_journey.py | 64 +++++++++++++++++++ salesflow-saas/docs/DOCKER_FULL_STACK.md | 40 ++++++++++++ salesflow-saas/docs/LAUNCH_CHECKLIST.md | 6 ++ salesflow-saas/docs/MARKET_POSITIONING_AR.md | 25 ++++++++ salesflow-saas/docs/STAGING_ENV_CHECKLIST.md | 26 ++++++++ salesflow-saas/frontend/.env.staging.example | 4 ++ .../memory/runbooks/ROLLBACK_AND_GO_LIVE.md | 31 +++++++++ .../runbooks/production-deployment-guide.md | 2 +- 12 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 salesflow-saas/.env.staging.example create mode 100644 salesflow-saas/backend/tests/test_launch_staging_product_journey.py create mode 100644 salesflow-saas/docs/DOCKER_FULL_STACK.md create mode 100644 salesflow-saas/docs/MARKET_POSITIONING_AR.md create mode 100644 salesflow-saas/docs/STAGING_ENV_CHECKLIST.md create mode 100644 salesflow-saas/frontend/.env.staging.example create mode 100644 salesflow-saas/memory/runbooks/ROLLBACK_AND_GO_LIVE.md diff --git a/salesflow-saas/.env.staging.example b/salesflow-saas/.env.staging.example new file mode 100644 index 00000000..bd649645 --- /dev/null +++ b/salesflow-saas/.env.staging.example @@ -0,0 +1,36 @@ +# Staging template — copy to `.env` on the staging host (never commit `.env`). +# Full variable list: `.env.example`. Phase-2 integrations checklist: `docs/INTEGRATION_MASTER_AR.md` +# and `backend/.env.phase2.example`. + +ENVIRONMENT=staging +DEFAULT_TIMEZONE=Asia/Riyadh + +# Docker Compose: keep @db / @redis hostnames. Bare metal: use 127.0.0.1 and matching ports. +DB_NAME=salesflow +DB_USER=salesflow +DB_PASSWORD=REPLACE_ME_STRONG +DATABASE_URL=postgresql+asyncpg://salesflow:REPLACE_ME_STRONG@db:5432/salesflow + +REDIS_URL=redis://redis:6379/0 + +SECRET_KEY=REPLACE_ME_RANDOM_32PLUS_CHARS +ACCESS_TOKEN_EXPIRE_MINUTES=30 +REFRESH_TOKEN_EXPIRE_DAYS=7 + +# Public URLs for your staging domain (CORS + webhooks) +API_URL=https://api-staging.example.com +FRONTEND_URL=https://app-staging.example.com +NEXT_PUBLIC_API_URL=https://api-staging.example.com +WEBHOOK_BASE_URL=https://api-staging.example.com/api/v1/webhooks + +# LLM — set at least one real key for AI features in staging +OPENAI_API_KEY= +GROQ_API_KEY= + +# Channels — fill only what you will smoke-test (see launch checklist) +WHATSAPP_API_TOKEN= +WHATSAPP_PHONE_NUMBER_ID= +WHATSAPP_VERIFY_TOKEN= + +SMTP_USER= +SMTP_PASSWORD= diff --git a/salesflow-saas/README.md b/salesflow-saas/README.md index 51260773..c79bd6ea 100644 --- a/salesflow-saas/README.md +++ b/salesflow-saas/README.md @@ -28,10 +28,12 @@ Frontend: `http://localhost:3000` **If the browser shows connection refused on `:3000` or `:8000`:** nothing is listening on that port yet. Start the stack (`docker compose up` from this folder) or run `uvicorn` / `npm run dev` manually. Confirm with `curl -sSf http://127.0.0.1:8000/api/v1/health` and ensure the browser is on the same machine as the server (not WSL/remote without port forwarding). -**Without Docker:** install Python 3.12+ and Node 22+, copy `.env` and `frontend/.env.local`, run Postgres/Redis (or point `DATABASE_URL` / `REDIS_URL` at existing instances), then `cd backend && uvicorn app.main:app --reload --host 0.0.0.0 --port 8000` and `cd frontend && npm run dev`. +**Without Docker:** install Python 3.12+ and Node 22+, copy `.env` and `frontend/.env.local`, run Postgres/Redis (or point `DATABASE_URL` / `REDIS_URL` at existing instances), then `cd backend && uvicorn app.main:app --reload --host 0.0.0.0 --port 8000` and `cd frontend && npm run dev`. If `DATABASE_URL` uses SQLite (`sqlite+aiosqlite`), the API runs `init_db()` on startup so tables exist for local smoke tests; production should use Postgres + Alembic migrations. **E2E locally:** after `npm ci`, run `npx playwright install chromium` once, then `npm run test:e2e` (matches CI). +**Staging env templates:** `.env.staging.example` (repo root) and `frontend/.env.staging.example` — copy to `.env` / `frontend/.env.local` on the host; see `docs/STAGING_ENV_CHECKLIST.md`. + **Customer onboarding (B2B):** `GET /api/v1/customer-onboarding/journey` and `docs/CUSTOMER_OS_ONBOARDING_AR.md`. Dashboard tab: **مسار التشغيل مع العميل**. **Launch verification:** see `docs/LAUNCH_CHECKLIST.md`. From `salesflow-saas`: copy `frontend/.env.example` to `frontend/.env.local` and set `NEXT_PUBLIC_API_URL`. Run `.\verify-launch.ps1 -HttpCheck -SoftReady` (use `-BaseUrl` if the API is not on port 8000). diff --git a/salesflow-saas/backend/app/database.py b/salesflow-saas/backend/app/database.py index 21354d97..26029b5f 100644 --- a/salesflow-saas/backend/app/database.py +++ b/salesflow-saas/backend/app/database.py @@ -61,6 +61,8 @@ async def get_db(): async def init_db(): + import app.models # noqa: F401 — register all models on Base.metadata before create_all + async with engine.begin() as conn: if not IS_SQLITE: for ext in ["CREATE EXTENSION IF NOT EXISTS vector", diff --git a/salesflow-saas/backend/app/main.py b/salesflow-saas/backend/app/main.py index 77466677..4d900712 100644 --- a/salesflow-saas/backend/app/main.py +++ b/salesflow-saas/backend/app/main.py @@ -12,6 +12,7 @@ from contextlib import asynccontextmanager import asyncio from app.config import get_settings +from app.database import IS_SQLITE, init_db from app.api.v1.router import api_router from app.flows.self_improvement_flow import self_improvement_flow from app.middleware.internal_api import InternalApiTokenMiddleware @@ -70,6 +71,8 @@ async def lifespan(app: FastAPI): print(f" Environment: {settings.ENVIRONMENT}") print(f" LLM Primary: {settings.LLM_PRIMARY_PROVIDER}") print(f" LLM Fallback: {settings.LLM_FALLBACK_PROVIDER}") + if IS_SQLITE: + await init_db() yield # Shutdown stop_event.set() diff --git a/salesflow-saas/backend/tests/test_launch_staging_product_journey.py b/salesflow-saas/backend/tests/test_launch_staging_product_journey.py new file mode 100644 index 00000000..86766760 --- /dev/null +++ b/salesflow-saas/backend/tests/test_launch_staging_product_journey.py @@ -0,0 +1,64 @@ +""" +Staging / CI — مسار منتج واحد + دخان قناة (بريد عبر outreach بدون إرسال SMTP). + +يُكمّل test_new_subscriber_journey ويُثبت أن القنوات تستجيب بعد تسجيل مستخدم. +""" + +from __future__ import annotations + +import uuid + +import pytest +from httpx import ASGITransport, AsyncClient + +from app.main import app + + +@pytest.mark.launch +@pytest.mark.asyncio +async def test_staging_happy_path_dashboard_and_email_channel_draft(): + suffix = uuid.uuid4().hex[:14] + email = f"staging_path_{suffix}@dealix.journey.test" + password = "Staging_Secure_Pass_9" + + transport = ASGITransport(app=app) + async with AsyncClient(transport=transport, base_url="http://test") as ac: + reg = await ac.post( + "/api/v1/auth/register", + json={ + "company_name": f"Staging Path Co {suffix}", + "company_name_ar": "شركة مسار الاختبار", + "full_name": "مالك الاختبار", + "email": email, + "password": password, + "phone": "0501112233", + "industry": "real_estate", + }, + ) + assert reg.status_code == 200, reg.text + token = reg.json()["access_token"] + + dash = await ac.get( + "/api/v1/dashboard/overview", + headers={"Authorization": f"Bearer {token}"}, + ) + assert dash.status_code == 200 + + outreach = await ac.post( + "/api/v1/outreach", + json={ + "channel": "email", + "lead": { + "name": "عميل تجريبي", + "company": "عقار الرياض", + "sector": "عقار", + }, + "campaign_type": "cold_intro", + "language": "ar", + }, + ) + assert outreach.status_code == 200, outreach.text + body = outreach.json() + assert body.get("channel") == "email" + assert "subject" in body and "body" in body + assert len(body["subject"]) > 0 and len(body["body"]) > 0 diff --git a/salesflow-saas/docs/DOCKER_FULL_STACK.md b/salesflow-saas/docs/DOCKER_FULL_STACK.md new file mode 100644 index 00000000..3712ec5d --- /dev/null +++ b/salesflow-saas/docs/DOCKER_FULL_STACK.md @@ -0,0 +1,40 @@ +# Dealix — تشغيل الستاك الكامل بـ Docker Compose + +من مجلد `salesflow-saas`: + +```bash +cp .env.example .env +# عدّل .env (SECRET_KEY، كلمات مرور DB، مفاتيح اختيارية) + +docker compose up -d --build +docker compose ps +``` + +## قاعدة البيانات والبذور + +```bash +make migrate +make seed +``` + +## Celery + +الخدمات: `celery_worker`, `celery_beat`. للتحقق: + +```bash +docker compose logs -f celery_worker --tail=50 +``` + +إذا كانت الميزات (تسلسلات، مهام مجدولة) لا تعمل، راجع أن Redis و`REDIS_URL` سليمة وأن الـ worker يعمل دون أخطاء متكررة. + +## إيقاف التشغيل + +```bash +docker compose down +# مع حذف الحجم (احذر — يمسح بيانات Postgres المحلية): +# docker compose down -v +``` + +## بيئة بدون Docker + +على أجهزة التطوير التي لا تتوفر فيها Docker، استخدم نفس أوامر CI: `pytest` مع SQLite و`npm run build`، وتشغيل `uvicorn` محلياً مع Postgres/Redis منفصلين أو قاعدة SQLite للاختبارات فقط — لا يغني ذلك عن اختبار staging حقيقي قبل الإنتاج. diff --git a/salesflow-saas/docs/LAUNCH_CHECKLIST.md b/salesflow-saas/docs/LAUNCH_CHECKLIST.md index d197a938..5e769a51 100644 --- a/salesflow-saas/docs/LAUNCH_CHECKLIST.md +++ b/salesflow-saas/docs/LAUNCH_CHECKLIST.md @@ -10,6 +10,7 @@ - [ ] تشغيل من **أحدث** كود في المستودع: `cd backend && py -m uvicorn app.main:app --host 0.0.0.0 --port 8000` +- [ ] **SQLite محلي فقط:** عند `DATABASE_URL=sqlite+aiosqlite://...` يُنشئ التطبيق الجداول عند الإقلاع (`init_db`) لتسهيل الاختبار؛ **الإنتاج يستخدم Postgres + Alembic** (`make migrate`) وليس الاعتماد على `create_all`. - [ ] إذا ظهر **404** على `/api/v1/marketing/hub` أو `/api/v1/strategy/summary` فالعملية غالباً **قديمة** — أعد تشغيل `uvicorn` بعد `git pull`. - [ ] اختبار HTTP: `py scripts/full_stack_launch_test.py --http-only --soft-ready` @@ -31,6 +32,11 @@ - [ ] مراقبة `/api/v1/health` و `/api/v1/ready`. - [ ] مراجعة `go-live-gate` عند التكاملات الحقيقية (قد يعيد 403 حتى اكتمال التهيئة — متوقع). +- [ ] خطة التراجع والقطع: `memory/runbooks/ROLLBACK_AND_GO_LIVE.md`. + +## 6. تمييز السوق (قبل الإعلان) + +- [ ] مراجعة `docs/MARKET_POSITIONING_AR.md` ومواءمة النسخ مع الميزات المفعّلة فعلياً. --- diff --git a/salesflow-saas/docs/MARKET_POSITIONING_AR.md b/salesflow-saas/docs/MARKET_POSITIONING_AR.md new file mode 100644 index 00000000..c6e60e3b --- /dev/null +++ b/salesflow-saas/docs/MARKET_POSITIONING_AR.md @@ -0,0 +1,25 @@ +# Dealix — تمييز السوق والرسالة (عربي) + +## قصة قطاع: عقار في الرياض + +**المشكلة:** فرق المبيعات تتلقى استفسارات من واتساب والموقع، لكن المتابعة غير موحّدة، والتأهيل يدوي، والامتثال لموافقات العملاء (PDPL) غير موثّق. + +**الحل مع Dealix:** مسار واحد من Lead → تأهيل بالذكاء الاصطناعي (عربي) → تسلسل رسائل بحكم PDPL → لوحة للصفقات والموافقات. العملاء يرون **وقت أقل على التتبع** و**زيادة وضوح Pipeline**. + +## مقارنة سريعة مع CRM عام (HubSpot-style) + +| البعد | CRM عام | Dealix | +|--------|---------|--------| +| العربية والـ RTL | غالباً إضافة | أولاً في الواجهة والقوالب | +| واتساب كقناة أولى | يحتاج تكاملات وإعداد طويل | مسارات وقنوات مدمجة في المنتج (انظر التكاملات في `docs/INTEGRATION_MASTER_AR.md`) | +| PDPL والموافقات | مسؤولية العميل غالباً | طبقة خدمات ووثائق موجهة للسوق السعودي | +| «يوم واحد» | استيراد جهات اتصال | تشغيل سريع مع قوائم التحقق والـ staging templates | + +## الامتثال كميزة + +- التحقق من الموافقة قبل أي إرسال صادر مذكور في سياسة المنتج (`services/pdpl`). +- للمواد الخارجية: اذكر **PDPL** و**تسجيل الموافقات** بلغة عملاء الأعمال، دون وعود بميزات غير مفعّلة في البيئة التي تنشرونها. + +## مواءمة النسخ مع المنتج الحي + +قبل الحملات: راجع المسارات العامة (`/`، `/marketers`، `/strategy`) و`GET /api/v1/value-proposition/` و`GET /api/v1/customer-onboarding/journey` وتأكد أن الوعود في التسويق تطابق ما هو مُفعّل في الإنتاج. diff --git a/salesflow-saas/docs/STAGING_ENV_CHECKLIST.md b/salesflow-saas/docs/STAGING_ENV_CHECKLIST.md new file mode 100644 index 00000000..75119639 --- /dev/null +++ b/salesflow-saas/docs/STAGING_ENV_CHECKLIST.md @@ -0,0 +1,26 @@ +# Dealix — قائمة تهيئة بيئة Staging + +استخدم هذا الملف مع [LAUNCH_CHECKLIST.md](LAUNCH_CHECKLIST.md) و [INTEGRATION_MASTER_AR.md](INTEGRATION_MASTER_AR.md). + +## 1) ملفات البيئة + +| الملف | الإجراء | +|--------|---------| +| جذر `salesflow-saas` | انسخ [`.env.staging.example`](../.env.staging.example) إلى `.env` واستبدل القيم | +| `frontend/` | انسخ [`frontend/.env.staging.example`](../frontend/.env.staging.example) إلى `frontend/.env.local` | +| تكاملات موسعة | راجع `backend/.env.phase2.example` واملأ الأقسام التي ستفعّلها فقط | + +## 2) CORS + +- `FRONTEND_URL` في `.env` يجب أن يطابق أصل الواجهة (مثلاً `https://app-staging.example.com`). +- إن تغيّر النطاق، حدّث إعدادات CORS في الباكند إن لزم. + +## 3) بعد التشغيل + +- `GET /api/v1/health` و `GET /api/v1/ready` +- سكربت اختياري: `python scripts/full_stack_launch_test.py --http-only --soft-ready` مع `DEALIX_BASE_URL` +- جولة يدوية سريعة للواجهة (RTL): `/`، `/landing`، `/marketers`، `/strategy`، `/login`، `/register`، `/dashboard`، `/settings`، `/privacy`، `/terms` + +## 4) قنوات حقيقية + +لا تفعّل واتساب/بريد إنتاجي حتى اكتمال فحوص PDPL والموافقات. للـ staging اختبر عنواناً داخلياً أو رقم sandbox. diff --git a/salesflow-saas/frontend/.env.staging.example b/salesflow-saas/frontend/.env.staging.example new file mode 100644 index 00000000..6f06bd7e --- /dev/null +++ b/salesflow-saas/frontend/.env.staging.example @@ -0,0 +1,4 @@ +# Staging — copy to `.env.local` (or set in hosting dashboard). Do not commit `.env.local`. +# Must be a URL the browser can reach (HTTPS in staging/production). + +NEXT_PUBLIC_API_URL=https://api-staging.example.com diff --git a/salesflow-saas/memory/runbooks/ROLLBACK_AND_GO_LIVE.md b/salesflow-saas/memory/runbooks/ROLLBACK_AND_GO_LIVE.md new file mode 100644 index 00000000..ce7ce0f4 --- /dev/null +++ b/salesflow-saas/memory/runbooks/ROLLBACK_AND_GO_LIVE.md @@ -0,0 +1,31 @@ +# Dealix — بوابة الإنتاج، المراقبة، والتراجع + +**مرجع:** `docs/LAUNCH_CHECKLIST.md`، `GET /api/v1/autonomous-foundation/integrations/go-live-gate`، `GET /api/v1/autonomous-foundation/integrations/live-readiness`. + +## ما قبل القطع (go-live gate) + +1. دمج `main` مع CI أخضر (باكند pytest، فرونت lint/build، Playwright E2E). +2. Postgres: `make migrate` (أو ما يعادله في الاستضافة) — **لا** تعتمد على `init_db()` في SQLite للإنتاج. +3. ضبط `.env` و`frontend/.env.local` (`NEXT_PUBLIC_API_URL`، `FRONTEND_URL`، CORS). +4. توقع **403** من `go-live-gate` حتى تكتمل التكاملات الحرجة — هذا متوقع إذا كانت البيئة غير مهيأة بالكامل؛ راجع `live-readiness` للتفاصيل. + +## النشر + +- باكند: صورة Docker أو عملية `uvicorn` خلف reverse proxy مع TLS. +- فرونت: بناء Next.js (`npm run build`) أو منصة الاستضافة المختارة؛ نفس متغيرات الـ API العامة. + +## المراقبة بعد الإطلاق + +- `GET /api/v1/health` — الخدمة حية. +- `GET /api/v1/ready` — جاهزية التبعيات (قاعدة، Redis، إلخ حسب التطبيق). +- سجلات الأخطاء (مثلاً Sentry) ومراقبة معدل 5xx على المسارات الحرجة. + +## التراجع (rollback) + +1. **التطبيق:** إعادة نشر الإصدار السابق من صورة Docker / commit المعتمد. +2. **قاعدة البيانات:** إن وُجدت ترحيلات Alembic تسبب خللاً، نفّذ `alembic downgrade` إلى المراجعة المعروفة بالاستقرار (بعد أخذ نسخة احتياطية). +3. **الإعدادات:** أعد القيم السابقة للأسرار في مدير الأسرار إن تغيّرت أثناء القطع. + +## فرع الكود للنشر + +استخدم **`main`** كمصدر للإنتاج بعد الدمج؛ لا تعتمد على فروع مؤقتة قديمة في سكربتات النشر. diff --git a/salesflow-saas/memory/runbooks/production-deployment-guide.md b/salesflow-saas/memory/runbooks/production-deployment-guide.md index 39eeed39..36cefa39 100644 --- a/salesflow-saas/memory/runbooks/production-deployment-guide.md +++ b/salesflow-saas/memory/runbooks/production-deployment-guide.md @@ -41,7 +41,7 @@ apt install git -y # 5. انسخ المشروع git clone https://github.com/VoXc2/system-prompts-and-models-of-ai-tools.git cd system-prompts-and-models-of-ai-tools -git checkout claude/complete-system-prompts-wqJCm +git checkout main cd salesflow-saas ```