Files
clinicpro/.claude/prompt/sms-settings-apikey-env-only.md
T

312 lines
16 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.
# ساده‌سازی «تنظیمات پیامک (SMS)» — فقط API Key از env
## پروژه
`clinicpro` (Backend Symfony + پنل ادمین React)
## زمینه
در پنل ادمین (`https://clinic-pro.ddev.site/admin` → صفحه تنظیمات) بخش **«تنظیمات پیامک (SMS)»** الان چهار مورد قابل‌ویرایش دارد که همه در جدول DB `site_config` ذخیره می‌شوند:
- `sms_provider` (انتخاب سرویس: کاوه‌نگار / رنگینه)
- `sms_price_rials` (قیمت هر پیامک)
- `kavenegar_api_key` (کلید API)
- `kavenegar_sender` (شماره فرستنده)
مقدار API Key عملاً باید یک راز (secret) باشد و جای آن env است، نه دیتابیس یا فرم قابل‌ویرایش پنل. تصمیم نهایی:
- **تنها `KAVENEGAR_API_KEY` در env بماند.** بقیه env های SMS حذف شوند.
- در پنل، کلید فقط **read-only** نمایش داده شود (وضعیت «تنظیم‌شده / تنظیم‌نشده») و از env خوانده شود؛ دیگر در DB ذخیره نشود.
- سه مورد دیگر (provider، price، sender) از **UI و Backend به‌طور کامل حذف** شوند.
- خودِ کارت/بخش «تنظیمات پیامک (SMS)» باقی بماند.
## هدف
1. API Key فقط از env (`KAVENEGAR_API_KEY`) — read-only در پنل، بدون ذخیره در DB.
2. حذف کامل `sms_provider`، `sms_price_rials`، `kavenegar_sender` (و کلیدهای rangineh) از UI و Backend config.
3. Provider همیشه `kavenegar`. Sender خالی → کاوه‌نگار از خط پیش‌فرض حساب استفاده می‌کند.
4. `sms_price_rials` که قبلاً از config خوانده می‌شد، تبدیل به ثابت (constant) شود تا کیف‌پول پیامک نشکند.
5. همه فایل‌های env به‌روزرسانی شوند: فقط `KAVENEGAR_API_KEY` بماند.
## فایل‌های مرتبط
| فایل | نقش | تغییر |
|------|-----|-------|
| `assets/admin/pages/SettingsPage.tsx` | فرم تنظیمات پنل | حذف ۳ فیلد، تبدیل API Key به read-only status |
| `assets/admin/types/index.ts` | تایپ‌ها | حذف `sms_price_rials` |
| `src/Config/Controller/SiteConfigController.php` | endpoint `/api/v1/admin/settings` | حذف کلیدهای SMS از `ALLOWED_KEYS`؛ افزودن flag read-only |
| `src/Config/Repository/SiteConfigRepository.php` | مقادیر پیش‌فرض config | حذف کلیدهای SMS از `DEFAULTS` |
| `src/Sms/Provider/KavehNegarProvider.php` | provider کاوه‌نگار | خواندن key/sender فقط از env، نه configRepo |
| `src/Sms/Controller/SmsWalletController.php` | balance/price | `sms_price_rials` از ثابت به‌جای config |
| `config/services.yaml` | wiring provider | تنظیم binding های env |
| `.env` و بقیه `.env.*` | متغیرهای محیطی | فقط `KAVENEGAR_API_KEY` |
| `docs/api/sms.md` | مستندات | ذکر منبع جدید API Key |
---
## وضعیت فعلی (کد واقعی)
### ۱. فرم پنل — `assets/admin/pages/SettingsPage.tsx`
Zod schema (خطوط ۴۰–۴۳):
```tsx
sms_provider: z.string(),
kavenegar_api_key: z.string(),
kavenegar_sender: z.string(),
sms_price_rials: z.string(),
```
interface (خطوط ۶۵–۶۸) + reset defaults (۱۲۳–۱۲۶) + reset onClick (۵۴۴–۵۴۷) هم همین ۴ کلید را دارند.
بخش UI (خطوط ۴۷۱–۵۲۰):
```tsx
{/* تنظیمات پیامک */}
<div className="card card-pad">
{/* ... هدر «تنظیمات پیامک (SMS)» ... */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
<div>
<label>سرویس پیامک</label>
<select {...register('sms_provider')}>
<option value="kavenegar">کاوه‌نگار</option>
<option value="rangineh">رنگینه</option>
</select>
</div>
<div>
<label>قیمت هر پیامک (ریال)</label>
<input {...register('sms_price_rials')} type="number" ... />
</div>
</div>
<div style={{ marginTop: '1rem' }}>
<div>تنظیمات کاوه‌نگار</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0.75rem' }}>
<div>
<label>API Key</label>
<input {...register('kavenegar_api_key')} type={showKavenegarKey ? 'text' : 'password'} ... />
{/* دکمه نمایش/پنهان */}
</div>
<div>
<label>شماره فرستنده</label>
<input {...register('kavenegar_sender')} ... />
</div>
</div>
</div>
</div>
```
### ۲. `src/Config/Repository/SiteConfigRepository.php` — `DEFAULTS` (خطوط ۳۲–۳۷)
```php
// sms provider
'sms_provider' => 'kavenegar',
'kavenegar_api_key' => '',
'kavenegar_sender' => '',
'rangineh_api_key' => '',
'rangineh_sender' => '',
'sms_price_rials' => '500',
```
### ۳. `src/Config/Controller/SiteConfigController.php` — `ALLOWED_KEYS` (خطوط ۳۸–۴۴)
```php
// sms provider
'sms_provider',
'kavenegar_api_key',
'kavenegar_sender',
'rangineh_api_key',
'rangineh_sender',
'sms_price_rials',
```
### ۴. `src/Sms/Provider/KavehNegarProvider.php` (خطوط ۱۳–۲۴)
```php
public function __construct(
private readonly HttpClientInterface $httpClient,
private readonly SiteConfigRepository $configRepo,
private readonly LoggerInterface $logger,
private readonly ?string $apiKey = null,
private readonly ?string $sender = null,
) {}
private function key(): string { return $this->configRepo->get('kavenegar_api_key') ?: ($this->apiKey ?? ''); }
private function sender(): string { return $this->configRepo->get('kavenegar_sender') ?: ($this->sender ?? ''); }
```
### ۵. `src/Sms/Controller/SmsWalletController.php` (خط ۵۶)
```php
$smsPriceRials = (int) ($this->configRepo->get('sms_price_rials') ?? 500);
```
### ۶. `config/services.yaml` (خطوط ۹۲–۹۹)
```yaml
App\Sms\Provider\KavehNegarProvider:
arguments:
$apiKey: '%env(default::KAVENEGAR_API_KEY)%'
$sender: '%env(default::KAVENEGAR_SENDER)%'
App\Sms\Provider\RanginehProvider:
arguments:
$apiKey: '%env(default::RANGINEH_API_KEY)%'
$sender: '%env(default::RANGINEH_SENDER)%'
```
### ۷. env فعلی — `.env` (خطوط ۳۸–۴۲ و ۶۰–۶۴)
```
###> SMS ###
KAVENEGAR_API_KEY=change_me
RANGINEH_API_KEY=change_me
SMS_PROVIDER=kavenegar
###< SMS ###
...
# SMS
KAVENEGAR_API_KEY=test_key
KAVENEGAR_SENDER=1000596446
RANGINEH_API_KEY=test_key
RANGINEH_SENDER=3000
```
> توجه: `.env` هم بلوک `###> SMS ###` دارد هم یک بخش دوم پایین فایل. هر دو باید یکی شوند.
---
## وظایف
### ۱. Backend — provider کاوه‌نگار فقط از env
در `src/Sms/Provider/KavehNegarProvider.php`:
- متد `key()` و `sender()` دیگر از `configRepo` نخوانند؛ فقط از env (constructor):
```php
private function key(): string { return $this->apiKey ?? ''; }
private function sender(): string { return $this->sender ?? ''; }
```
- چون `SiteConfigRepository` دیگر در این کلاس استفاده نمی‌شود، `use App\Config\Repository\SiteConfigRepository;` و پارامتر `private readonly SiteConfigRepository $configRepo,` از constructor حذف شود.
- کامنت خطوط ۱۷–۱۸ («the real key normally comes from DB site config») هم اصلاح شود که حالا فقط از env می‌آید.
### ۲. Backend — حذف کلیدهای SMS از config
- `src/Config/Repository/SiteConfigRepository.php`: کل بلوک `// sms provider` (۶ کلید: `sms_provider`, `kavenegar_api_key`, `kavenegar_sender`, `rangineh_api_key`, `rangineh_sender`, `sms_price_rials`) از `DEFAULTS` حذف شود.
- `src/Config/Controller/SiteConfigController.php`: همان ۶ کلید از `ALLOWED_KEYS` حذف شوند (دیگر از طریق PATCH قابل ذخیره نیستند).
### ۳. Backend — قیمت پیامک به ثابت
در `src/Sms/Controller/SmsWalletController.php`، چون `sms_price_rials` دیگر در config نیست، آن را ثابت کن:
```php
class SmsWalletController extends BaseController
{
private const SMS_PRICE_RIALS = 500;
...
// به‌جای config:
$smsPriceRials = self::SMS_PRICE_RIALS;
```
> `SmsWalletPage.tsx` (خط ۱۲۶) مقدار `balance.sms_price_rials` را از پاسخ همین endpoint می‌خواند؛ چون همچنان در پاسخ برگردانده می‌شود، نیازی به تغییر ندارد.
### ۴. Backend — نمایش وضعیت read-only کلید در پنل
چون API Key دیگر در DB نیست، برای اینکه پنل بتواند «تنظیم‌شده/نشده» را نشان دهد، در `SiteConfigController::get()` یک flag محاسبه‌شده اضافه کن (بدون افشای خودِ کلید):
```php
#[Route('/api/v1/admin/settings', methods: ['GET'])]
public function get(): JsonResponse
{
$config = $this->configRepo->getAll();
// API Key پیامک فقط از env؛ فقط وضعیت تنظیم‌بودن برگردانده می‌شود نه مقدار.
$config['sms_api_key_configured'] = ($_ENV['KAVENEGAR_API_KEY'] ?? '') !== '';
return $this->success($config);
}
```
> اگر پروژه برای خواندن env از سرویس/پارامتر خاصی استفاده می‌کند، از همان الگو استفاده کن؛ اگر نه، خواندن مستقیم از `$_ENV` قابل‌قبول است.
### ۵. `config/services.yaml`
- برای `KavehNegarProvider` فقط `$apiKey` از env بماند؛ خط `$sender` حذف شود (constructor مقدار پیش‌فرض `null` دارد → خط پیش‌فرض کاوه‌نگار):
```yaml
App\Sms\Provider\KavehNegarProvider:
arguments:
$apiKey: '%env(default::KAVENEGAR_API_KEY)%'
```
- binding های `RanginehProvider` (خطوط ۹۶–۹۹) که به env حذف‌شده اشاره می‌کنند حذف شوند. کلاس `RanginehProvider` و تزریق آن در `SmsService` دست‌نخورده بماند (فقط دیگر انتخاب نمی‌شود؛ `resolveProvider()` به‌صورت پیش‌فرض `kavenegar` برمی‌گرداند).
### ۶. Frontend — `assets/admin/pages/SettingsPage.tsx`
- از Zod schema (۴۰–۴۳): حذف `sms_provider`، `kavenegar_sender`، `sms_price_rials`. کلید `kavenegar_api_key` هم چون دیگر ارسال نمی‌شود از schema حذف شود.
- از interface تنظیمات (۶۵–۶۸)، reset defaults (۱۲۳–۱۲۶) و reset onClick (۵۴۴–۵۴۷): همان کلیدها حذف شوند.
- state مربوط به `showKavenegarKey` حذف شود (دیگر input رمزی نداریم).
- کارت «تنظیمات پیامک (SMS)» بماند اما محتوایش به یک نمایش read-only تبدیل شود که وضعیت را از `settings.sms_api_key_configured` می‌خواند:
```tsx
{/* تنظیمات پیامک */}
<div className="card card-pad">
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: '1.25rem' }}>
<div className="ico" style={{ background: 'var(--violet-bg)', color: 'var(--violet)', width: 36, height: 36, borderRadius: 10 }}>
<span style={{ fontSize: 16 }}>📱</span>
</div>
<h3 style={{ fontSize: 15 }}>تنظیمات پیامک (SMS)</h3>
</div>
<div>
<label style={{ fontSize: 12.5, fontWeight: 500, display: 'block', marginBottom: 4 }}>
کلید API کاوه‌نگار
</label>
<div style={{ fontSize: 13, color: 'var(--muted)' }}>
{settings?.sms_api_key_configured
? <span style={{ color: 'var(--success)' }}> تنظیم‌شده (از متغیر محیطی KAVENEGAR_API_KEY)</span>
: <span style={{ color: 'var(--danger)' }}> تنظیم‌نشده مقدار KAVENEGAR_API_KEY را در فایل env قرار دهید</span>}
</div>
<p style={{ fontSize: 11.5, color: 'var(--muted)', marginTop: 8 }}>
کلید API فقط از طریق متغیر محیطی سرور مدیریت می‌شود و در این پنل قابل ویرایش نیست.
</p>
</div>
</div>
```
- `assets/admin/types/index.ts` خط ۴۱۵: `sms_price_rials: number;` حذف شود. اگر تایپ تنظیمات، `sms_provider`/`kavenegar_api_key`/`kavenegar_sender` هم دارد، آن‌ها هم حذف و در صورت لزوم `sms_api_key_configured: boolean;` اضافه شود.
### ۷. به‌روزرسانی همه فایل‌های env
در **همه** این فایل‌ها فقط `KAVENEGAR_API_KEY` بماند و `KAVENEGAR_SENDER`، `RANGINEH_API_KEY`، `RANGINEH_SENDER`، `SMS_PROVIDER` حذف شوند:
`.env`, `.env.example`, `.env.dev`, `.env.local`, `.env.test`, `.env.coolify.example`, `.env.liara.example`
الگوی نهایی بلوک SMS (در `.env`):
```
###> SMS ###
KAVENEGAR_API_KEY=change_me
###< SMS ###
```
- در `.env` بخش تکراری پایین فایل (خطوط ۶۰–۶۴) هم پاک‌سازی شود؛ فقط یک `KAVENEGAR_API_KEY` باقی بماند.
- در `.env.example` کامنت خط ۴۱ اصلاح شود به این مضمون: «کلید کاوه‌نگار فقط از env خوانده می‌شود» (نه از DB).
- برای فایل‌های نمونه (`*.example`) مقدار placeholder مثل `KAVENEGAR_API_KEY=` یا `change_me` بگذار؛ در `.env.test` مقدار تستی (مثلاً `test_key`).
### ۸. مستندات — `docs/api/sms.md`
- ذکر شود API Key کاوه‌نگار فقط از env `KAVENEGAR_API_KEY` خوانده می‌شود.
- ذکر شود endpoint `/api/v1/admin/settings` دیگر کلیدهای `sms_provider`، `kavenegar_api_key`، `kavenegar_sender`، `rangineh_*`، `sms_price_rials` را نمی‌پذیرد؛ در پاسخ GET فیلد read-only `sms_api_key_configured` اضافه شده.
- provider ثابت `kavenegar` و قیمت پیامک ثابت `SMS_PRICE_RIALS = 500` مستند شود.
---
## نکات مهم
- **پاک‌سازی DB (اختیاری):** ردیف‌های قدیمی این کلیدها ممکن است در جدول `site_config` مانده باشند. حذفشان ضروری نیست (دیگر خوانده نمی‌شوند) اما برای تمیزی می‌توان با یک کوئری آن‌ها را حذف کرد. migration لازم نیست چون Entity تغییر نکرده.
- **Sender خالی:** با حذف `kavenegar_sender`، متد `send()` مقدار `sender=''` می‌فرستد؛ کاوه‌نگار در این حالت از خط پیش‌فرض حساب استفاده می‌کند. اگر خط اختصاصی لازم شد، بعداً می‌توان `KAVENEGAR_SENDER` را به env و binding برگرداند — طبق درخواست فعلی فقط API Key در env می‌ماند.
- **سازگاری پاسخ:** ساختار پاسخ‌ها با `$this->success()` حفظ شود (BaseController).
- **بررسی تایپ فرانت:** بعد از تغییر، `ddev exec npx tsc --noEmit --project tsconfig.json` خطا ندهد (کلیدهای حذف‌شده جایی رفرنس نشده باشند — خصوصاً `showKavenegarKey`).
- **بیلد:** `ddev exec yarn dev` برای تأیید بیلد JS/TS.
- **پاک کردن کش بعد از تغییر services.yaml/env:** `ddev exec php bin/console cache:clear`.
- **graphify:** بعد از پایان تغییرات `graphify update .` اجرا شود.