Files
clinicpro/.claude/prompt/otp-sms-site-name.md
T

150 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# افزودن اسم سایت شهر به پیامک کد تأیید (OTP) — به‌صورت اختیاری
## پروژه
`clinicpro` (Backend) — **cross-repo**؛ پرامپت همتای frontend: `nobat724_front/.claude/prompt/otp-sms-site-name.md`
## زمینه
سایت عمومی چند-دامنه‌ای است (هر شهر یک دامنه: `yasuj-nobat.ir`، `ahvaz-nobat.ir`، ...). هر شهر در جدول `cities` فیلد `site_name` دارد (مثل «یاسوج نوبت»). الان پیامک کد تأیید ورود ثابت است: `کد تأیید شما: {code}` و اسم سایت شهر ندارد.
هدف: بتوان **اختیاری** اسم سایتِ شهرِ درخواست‌کننده را در پیامک OTP نمایش داد — مثلاً «کد تأیید شما در یاسوج نوبت: ۱۲۳۴۵». اختیاری‌بودن از طریق **قالب قابل‌ویرایش پنل SMS** کنترل می‌شود: اگر ادمین `{site}` را در متن قالب بگذارد نمایش داده می‌شود، نگذارد نمی‌شود.
## مشکل / هدف
- درخواست `send-code` از دامنه‌ی شهر به API (`api.clinic-pro.ir`) می‌رسد → `Host` = دامنه‌ی API، نه شهر. پس backend خودش شهر را نمی‌فهمد.
- **راه‌حل:** frontend دامنه‌ی خودش را در body می‌فرستد؛ backend از روی `domain` در جدول `cities``site_name` را پیدا می‌کند و به‌عنوان متغیر `{site}` به قالب پیامک تزریق می‌کند. اگر شهری پیدا نشد یا `domain` نیامد → fallback به برند پیش‌فرض «نوبت ۷۲۴».
- متغیر `{site}` همیشه مقداردهی می‌شود؛ استفاده یا عدم‌استفاده‌اش به قالب بستگی دارد (اختیاری).
## فایل‌های مرتبط
| فایل | نقش |
|------|-----|
| `src/Auth/Controller/AuthController.php` | endpoint `POST /api/v1/user/send-code` — خواندن `domain` اختیاری از body |
| `src/Auth/Service/OtpService.php` | ساخت و ارسال پیامک OTP — resolve اسم سایت و تزریق `{site}` |
| `src/Location/Repository/CityRepository.php` | lookup شهر بر اساس `domain` |
| `src/Location/Entity/City.php` | دارای `getDomain()` و `getSiteName()` (آماده، تغییر نمی‌خواهد) |
| `src/Sms/Entity/SmsMessageTemplate.php` | ثبت `{site}` به‌عنوان متغیر مجاز قالب OTP |
| `docs/api/auth.md`, `docs/api/sms.md` | مستندسازی |
## وضعیت فعلی
`OtpService::sendCode` (src/Auth/Service/OtpService.php):
```php
public function sendCode(string $mobile): string
{
$uuid = Uuid::v4()->toRfc4122();
$code = $this->appEnv === 'dev' ? '12345' : str_pad((string) random_int(10000, 99999), 5, '0', STR_PAD_LEFT);
// ... cache set ...
if ($this->appEnv !== 'dev') {
$message = $this->smsText->resolve(SmsLog::TAG_OTP, ['code' => $code]);
$this->sms->dispatchAsync($mobile, $message, tag: SmsLog::TAG_OTP);
}
return $uuid;
}
```
`AuthController::sendCode` (src/Auth/Controller/AuthController.php:136):
```php
$data = json_decode($request->getContent(), true) ?? [];
$mobile = trim($data['mobile'] ?? '');
// ... validation + rate limit ...
$uuid = $this->otpService->sendCode($mobile);
```
قالب پیش‌فرض OTP (src/Sms/Entity/SmsMessageTemplate.php):
```php
SmsLog::TAG_OTP => [
'title' => 'کد تأیید ورود',
'body' => 'کد تأیید شما: {code}',
'variables' => ['code'],
],
```
resolver (src/Sms/Service/SmsTextResolver.php) هر `{key}` را با str_replace جایگزین می‌کند — پس `{site}` بدون تغییر resolver کار می‌کند.
## وظایف
### ۱. `OtpService` — resolve اسم سایت و تزریق `{site}`
- `CityRepository` را به constructor تزریق کن.
- امضای متد را به `sendCode(string $mobile, ?string $domain = null)` تغییر بده (پارامتر اختیاری → سازگاری عقب‌رو).
- اسم سایت را resolve کن و به متغیرهای قالب اضافه کن:
```php
public function sendCode(string $mobile, ?string $domain = null): string
{
// ... code + cache بدون تغییر ...
if ($this->appEnv !== 'dev') {
$site = $this->resolveSiteName($domain);
$message = $this->smsText->resolve(SmsLog::TAG_OTP, ['code' => $code, 'site' => $site]);
$this->sms->dispatchAsync($mobile, $message, tag: SmsLog::TAG_OTP);
}
return $uuid;
}
private function resolveSiteName(?string $domain): string
{
$default = 'نوبت ۷۲۴';
if (!$domain) {
return $default;
}
$domain = strtolower(trim(preg_replace('#^https?://#', '', $domain)));
$domain = preg_replace('#/.*$#', '', $domain); // strip path
$city = $this->cityRepo->findOneBy(['domain' => $domain]);
return $city?->getSiteName() ?: $default;
}
```
- **نکته:** `{site}` همیشه مقدار دارد؛ اگر قالب فعلی `{site}` نداشته باشد، این متغیر بی‌اثر است (اختیاری‌بودن حفظ می‌شود).
### ۲. `AuthController::sendCode` — خواندن `domain` اختیاری از body
```php
$mobile = trim($data['mobile'] ?? '');
$domain = isset($data['domain']) ? trim((string) $data['domain']) : null;
// ... validation + rate limit بدون تغییر ...
$uuid = $this->otpService->sendCode($mobile, $domain ?: null);
```
- `domain` اختیاری است؛ اگر نیامد، رفتار قبلی حفظ می‌شود.
- روی `domain` **validation سخت‌گیرانه لازم نیست** (فقط برای lookup استفاده می‌شود، نه اجرا)، اما trim و طول محدود (مثلاً حداکثر ۲۵۳ کاراکتر) اعمال کن.
### ۳. `SmsMessageTemplate` — ثبت `{site}` به‌عنوان متغیر مجاز OTP
قالب پیش‌فرض را **بدون** `{site}` نگه دار (opt-in)، اما `site` را به لیست `variables` اضافه کن تا در پنل ویرایش قالب به‌عنوان متغیر قابل‌استفاده نمایش داده شود:
```php
SmsLog::TAG_OTP => [
'title' => 'کد تأیید ورود',
'body' => 'کد تأیید شما: {code}', // پیش‌فرض بدون site
'variables' => ['code', 'site'], // site به‌عنوان متغیر اختیاری در پنل
],
```
- این‌طوری ادمین در پنل SMS می‌تواند body را به `کد تأیید شما در {site}: {code}` تغییر دهد تا اسم سایت نمایش داده شود — یا ندهد.
### ۴. مستندسازی
- `docs/api/auth.md`: در `POST /api/v1/user/send-code`، فیلد **اختیاری** `domain` را به request body اضافه کن (نوع string، توضیح: دامنه‌ی شهرِ درخواست‌کننده برای شخصی‌سازی متن پیامک؛ اگر نیامد از برند پیش‌فرض استفاده می‌شود). نمونه request/response را به‌روز کن.
- `docs/api/sms.md`: متغیر جدید `{site}` را برای قالب `OTP` مستند کن (مقدار = `site_name` شهرِ دامنه یا «نوبت ۷۲۴»).
## نکات مهم
- **backward-compatible**: هر دو `domain` (body) و پارامتر `sendCode` اختیاری‌اند؛ کلاینت‌های فعلی (پنل ادمین clinicpro، لاگین بدون domain) بدون تغییر کار می‌کنند و برند پیش‌فرض را می‌گیرند.
- محیط `dev`: پیامک ارسال نمی‌شود (کد ثابت `12345`) — این مسیر دست‌نخورده بماند.
- `{site}` هرگز خالی نباشد (fallback «نوبت ۷۲۴») تا اگر ادمین `{site}` را در قالب گذاشت، متن ناقص «... در : ...» تولید نشود.
- lookup دامنه: مقدار ارسالی frontend ممکن است `yasuj-nobat.ir` یا با `www.`/`https://` باشد — در `resolveSiteName` normalize کن (حذف پروتکل و path). اگر لازم شد `www.` را هم strip کن، اما مقادیر `cities.domain` بدون `www` ذخیره شده‌اند (مثل `yasuj-nobat.ir`).
- **پیامک OTP با پترن اپراتور:** provider پترن `{site}` را پشتیبانی می‌کند (تأییدشده). مطمئن شو ترتیب/نام متغیرها با پترن مصوب سامانه‌ی پیامکی هماهنگ است.
- تست:
```bash
ddev exec php -l src/Auth/Service/OtpService.php
ddev exec php -l src/Auth/Controller/AuthController.php
ddev exec php bin/console cache:clear
# ارسال با domain و بدون domain را در محیط staging بررسی کن (dev پیامک نمی‌فرستد)
```
- بعد از تغییر API، `docs/api/auth.md` و `docs/api/sms.md` را در همین session به‌روز کن (قانون استاندینگ پروژه).