Files
customerTicket/docs/PHONE_CONTACTS_API.md
2026-09-30 11:09:12 +03:30

144 lines
14 KiB
Markdown

# راهنمای API جست‌وجوی مخاطبان برای Phone
این API مخاطبان ثبت‌شده در پروژهٔ تیکتینگ را به شکل قابل استفاده برای «منابع بیرونی» Phone ارائه می‌کند. قرارداد پاسخ آن با نمونهٔ ابتدایی `EXTERNAL_CONTACT_APIS.md` سازگار است. این endpoint فقط اطلاعات را می‌خواند؛ ایجاد یا ویرایش مخاطب از طریق آن انجام نمی‌شود.
## نشانی و روش درخواست
| مورد | مقدار |
| --- | --- |
| روش | `GET` |
| مسیر | `/api/phone-contacts`، بدون `/` پایانی |
| نوع پاسخ موفق | `application/json` با کد `200` |
| احراز هویت | هدر `Authorization: Bearer <API_KEY>` |
| بدنهٔ درخواست | ندارد؛ پارامترها در URL ارسال می‌شوند |
نشانی کامل را با دامنهٔ همین پروژه بسازید؛ برای نمونه `https://crm.example.com/api/phone-contacts`. مقدار `<API_KEY>` کلیدی است که مدیر در دیتابیس این پروژه ثبت کرده است. نشست کاربر یا کوکی برای این API لازم نیست.
### پارامترهای query string
| پارامتر | ضروری | رفتار |
| --- | --- | --- |
| `search` | برای دریافت نتیجه، بله | متن جست‌وجو؛ فاصله‌های ابتدا و انتها حذف می‌شوند و حداکثر ۸۰ نویسهٔ اول بررسی می‌شود. مقدار خالی، غایب یا کوتاه‌تر از دو نویسه، نتیجهٔ خالی می‌دهد. |
| `limit` | خیر | بیشترین تعداد مخاطب در پاسخ. پیش‌فرض `50` است. مقدار عددی کمتر از `1` به `1` و بیشتر از `50` به `50` محدود می‌شود؛ مقدار غیرعددی خطای `400` می‌دهد. |
نمونهٔ درخواست HTTP:
```http
GET /api/phone-contacts?search=%D8%B9%D9%84%DB%8C&limit=50 HTTP/1.1
Host: crm.example.com
Authorization: Bearer <API_KEY>
Accept: application/json
```
نمونه با `curl`، با فرض اینکه کلید از قبل در متغیر محیطی همان shell موجود است:
```bash
curl --fail-with-body --get 'https://crm.example.com/api/phone-contacts' \
--data-urlencode 'search=علی رضایی' \
--data-urlencode 'limit=50' \
-H "Authorization: Bearer ${PHONE_CONTACTS_API_KEY}" \
-H 'Accept: application/json'
```
`--data-urlencode` متن فارسی و فاصله‌ها را برای URL کدگذاری می‌کند. کلید را در URL قرار ندهید.
## پاسخ موفق
ریشهٔ پاسخ همیشه یک شیء JSON با کلید `contacts` است. هر عضو این آرایه نام، شمارهٔ همراه و آرایهٔ شماره‌ها را دارد:
```json
{
"contacts": [
{
"name": "علی رضایی",
"mobile": "09123456789",
"phones": [
{"type": "همراه", "number": "09123456789"}
]
}
]
}
```
| فیلد | نوع | توضیح |
| --- | --- | --- |
| `contacts` | آرایه | حداکثر ۵۰ مخاطب، یا آرایهٔ خالی. |
| `contacts[].name` | رشته | نام نمایشی غیرخالی؛ فاصلهٔ دو سر حذف شده و حداکثر ۱۲۰ نویسه است. |
| `contacts[].mobile` | رشته | همان مقدار `phones[].number`، برای مصرف‌کننده‌ای که فیلد مستقیم شماره را می‌خواهد. نام این فیلد برای سازگاری حفظ شده و ممکن است مقدار آن تلفن ثابت باشد. |
| `contacts[].phones` | آرایه | در ساختار کنونی پروژه، یک شمارهٔ ثبت‌شدهٔ معتبر برای هر مخاطب. |
| `contacts[].phones[].type` | رشته | برای همراه ایرانی `همراه` و برای شمارهٔ دیگر `تلفن`. |
| `contacts[].phones[].number` | رشته | شمارهٔ قابل شماره‌گیری؛ شمارهٔ ایرانی با `0` محلی برگردانده می‌شود، مانند `0913...` یا `021...`. |
شماره پیش از ارسال به متن تبدیل می‌شود؛ فاصله، پرانتز، نقطه و خط تیره حذف می‌شوند و رقم‌های فارسی و عربی به رقم لاتین تبدیل می‌شوند. شمارهٔ نهایی باید حداکثر ۶۴ نویسه و فقط شامل رقم، `+`، `*` یا `#` باشد. مخاطب بدون نام یا شمارهٔ معتبر در پاسخ نمی‌آید. چون دیتابیس فعلی فیلد مستقل برای تلفن ثابت ندارد، هیچ شمارهٔ فرضی تولید نمی‌شود. شناسهٔ داخلی مخاطب نیز در پاسخ فرستاده نمی‌شود.
محدودیت ۵۰ مخاطب، نام ۱۲۰ نویسه‌ای و یک شمارهٔ حداکثر ۶۴ نویسه‌ای برای هر مخاطب، پاسخ را بسیار پایین‌تر از سقف پردازش ۱ MiB در Phone نگه می‌دارد.
اگر مخاطبی پیدا نشود، درخواست همچنان موفق است:
```http
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{"contacts":[]}
```
برای پاسخ‌های JSON موفق و خطاهای JSON این view، هدر `Cache-Control: no-store` تنظیم می‌شود.
## چه مخاطبانی نمایش داده می‌شوند؟
فقط رکوردهایی بررسی می‌شوند که پروفایل `CustomerProfile` داشته باشند، حساب مرتبط با آن‌ها از نوع `CUSTOMER` و فعال باشد، شمارهٔ همراه داشته باشد و دست‌کم یکی از فیلدهای نام، نام خانوادگی یا نام سازمان در پروفایل یا حساب آن‌ها پر باشد. سپس نام و شمارهٔ نهایی نیز اعتبارسنجی می‌شود.
نام نمایشی به این ترتیب انتخاب می‌شود: نام و نام خانوادگی پروفایل، سپس نام و نام خانوادگی حساب، سپس نام سازمان پروفایل. خروجی نام به ۱۲۰ نویسه محدود می‌شود.
اگر `search` شماره باشد، شکل‌های محلی با `0`، بین‌المللی با `+98` یا `0098`، و شکل ۱۰ رقمی بدون پیشوند برای شماره‌های ایرانی به یک قالب مقایسه تبدیل می‌شوند. این قاعده به `912` یا هیچ پیش‌شمارهٔ موبایلی محدود نیست؛ برای نمونه `02112345678`، `+982112345678` و `00982112345678` نیز معادل‌اند. رقم‌های فارسی/عربی و جداکننده‌هایی مانند فاصله و خط تیره پذیرفته می‌شوند. تطبیق با مقدار ذخیره‌شده **دقیق** است و شمارهٔ ایرانی در پاسخ با `0` محلی برمی‌گردد.
این API فقط شماره‌ای را می‌تواند پیدا کند که در دادهٔ موجود حساب مخاطب ذخیره شده باشد. مدل فعلی `User.mobile` هنگام ثبت معمول، شمارهٔ همراه ایرانی را می‌پذیرد و فیلد جداگانه‌ای برای خط ثابت ندارد؛ بنابراین جست‌وجوی `021...` فقط برای داده‌ای نتیجه می‌دهد که چنین شماره‌ای از قبل در آن موجود باشد. مدل مخاطب و فرم ثبت آن برای این قابلیت تغییر نکرده‌اند.
برای جست‌وجوی متنی، نام، نام خانوادگی و نام سازمان پروفایل و نام و نام خانوادگی حساب بررسی می‌شوند. برای جست‌وجوی چندواژه‌ای، **هر واژه** باید دست‌کم در یکی از این فیلدها پیدا شود. جست‌وجو نسبت به بزرگی حروف لاتین حساس نیست؛ شکل عربی «ي/ك» در متن ورودی به «ی/ک» تبدیل می‌شود. نتیجه‌هایی که یک فیلدشان دقیقاً با کل متن جست‌وجو برابر است، اول می‌آیند؛ سپس نتیجه‌هایی که فیلدی با آن متن شروع می‌شود و بعد سایر تطبیق‌ها. در هر گروه، شناسهٔ داخلی ترتیب را تعیین می‌کند.
`limit` سقف تعداد رکوردهای بررسی‌شده برای خروجی را تعیین می‌کند. اگر در میان آن‌ها رکوردی هنگام ساخت پاسخ به علت نام یا شمارهٔ نامعتبر کنار گذاشته شود، ممکن است تعداد نهایی کمتر از `limit` باشد. API صفحه‌بندی یا پارامتر `page` ندارد؛ Phone باید نتایج مورد نیاز را از همین پاسخ اول دریافت کند.
## کدهای پاسخ و خطاها
| وضعیت | علت | بدنهٔ معمول |
| --- | --- | --- |
| `200` | درخواست معتبر؛ با نتیجه یا بدون نتیجه | `{"contacts":[...]}` یا `{"contacts":[]}` |
| `400` | مقدار `limit` عدد صحیح نیست، مانند `limit=abc` | `{"error":"invalid limit"}` |
| `401` | هدر Bearer غایب/نامعتبر است، کلید اشتباه است یا کلید در پنل غیرفعال شده است | `{"error":"unauthorized"}` |
| `405` | روشی غیر از `GET`، مانند `POST` | پاسخ استاندارد Django برای روش نامجاز |
احراز هویت پیش از بررسی پارامترهای جست‌وجو انجام می‌شود؛ بنابراین درخواست بدون کلید حتی برای جست‌وجوی کوتاه نیز `401` می‌گیرد. خطاهای غیرمنتظرهٔ سرور ممکن است `5xx` باشند.
## ایجاد و تغییر کلید API در دیتابیس
1. Migration برنامه را با محیط مجازی همین مخزن اجرا کنید: `.venv/Scripts/python.exe manage.py migrate phone_contacts_api --noinput`. در محیط توسعهٔ محلی این migration قبلاً اعمال شده است؛ در هر محیط استقرار باید جداگانه اجرا شود.
2. با حساب دارای مجوز مدیریت وارد `/x5admin/` شوید و بخش «دسترسی API مخاطبان Phone» ← «کلید API مخاطبان Phone» را باز کنید.
3. هنگام ایجاد، یک کلید تصادفی با طول ۳۲ تا ۲۵۶ نویسه در فیلد «کلید جدید» وارد و گزینهٔ «فعال» را روشن کنید. در هر زمان فقط یک رکورد کلید قابل تعریف است.
4. برای تعویض کلید، همان رکورد را ویرایش و مقدار جدید وارد کنید. کلید قدیمی پس از ذخیره دیگر پذیرفته نمی‌شود. اگر فیلد را خالی بگذارید، کلید فعلی حفظ می‌شود. خاموش کردن «فعال» دسترسی را بلافاصله قطع می‌کند.
مقدار خام کلید پس از ذخیره در پنل قابل بازیابی نیست و فقط هش آن در دیتابیس می‌ماند. کلیدی را که ثبت می‌کنید از مسیر امن به مسئول تنظیم Phone منتقل کنید. کلید در متغیر محیطی Phone نگهداری می‌شود، اما **منبع اعتبارسنجی این API دیتابیس همین پروژه است**؛ تغییر کلید در پنل پروژه باید با تغییر مقدار نزد Phone همراه باشد.
## تنظیم منبع در Phone
در پنل «منابع بیرونی» Phone، یک منبع با این مقادیر تعریف کنید:
| فیلد پنل Phone | مقدار |
| --- | --- |
| آدرس ثابت API | `https://<دامنه-این-پروژه>/api/phone-contacts` |
| روش درخواست | `GET` |
| پارامترهای URL | `{"search":"{{query}}","limit":50}` |
| هدرها | `{"Authorization":"Bearer {{env:PHONE_EXTERNAL_CRM_TOKEN}}","Accept":"application/json"}` |
| بدنهٔ POST | `{}` |
| مسیر فهرست | `contacts` |
| مسیر نام مخاطب | `name` |
| نگاشت شماره‌ها | `[{"path":"phones[]","value_path":"number","label_path":"type"}]` |
مقدار `PHONE_EXTERNAL_CRM_TOKEN` در محیط اجرای Phone باید با **کلید خامی** که در پنل همین پروژه ثبت شده برابر باشد. Phone از سرور خود به این API درخواست می‌فرستد؛ بنابراین DNS، TLS و دسترسی شبکه از **میزبان Phone** به دامنهٔ این پروژه باید برقرار باشد. دستور `curl` بالا را نیز ترجیحاً از همان میزبان اجرا کنید.
## دامنهٔ دسترسی و محدودیت‌ها
این API با یک کلید مشترک کار می‌کند و هویت کاربران Phone را دریافت نمی‌کند. در نتیجه هر دارندهٔ کلید می‌تواند مخاطبان واجد شرایط را جست‌وجو کند؛ مجوزهای دیدن شماره در رابط Phone، محدودسازی داده‌های خروجی این API نیستند. کلید را فقط در سامانهٔ مورد اعتماد قرار دهید و در صورت افشا فوراً آن را در پنل تغییر دهید.
این endpoint فقط `GET` و احراز هویت Bearer را پیاده‌سازی می‌کند. شمارهٔ دفتر، چند شماره برای یک مخاطب، صفحه‌بندی، جست‌وجوی اختصاصی بر اساس واحد سازمانی و ایجاد/ویرایش مخاطب در این API وجود ندارد. برای قالب‌های دیگر پاسخ یا داده‌های بیشتر باید منبع دادهٔ متناظر در پروژه وجود داشته باشد.