feat: Add tagging system for SMS logs and templates

- Introduced a `tag` field in the `SmsLog` entity to categorize SMS messages.
- Updated the `SmsService` to handle the new `tag` parameter during SMS dispatch.
- Implemented a `SmsTextResolver` service to resolve SMS message templates based on tags.
- Created a new `SmsMessageTemplate` entity for editable SMS templates with placeholders.
- Added endpoints for managing SMS message templates in the admin panel.
- Enhanced existing SMS dispatching methods across various controllers to utilize the tagging system.
- Migrated the database to include the new `tag` field and created a seeding command for default SMS templates.
- Updated admin API to filter SMS logs by tag and include tag information in responses.
This commit is contained in:
hamed
2026-06-19 20:42:04 +03:30
parent da57c554c1
commit fa332f7fa1
22 changed files with 846 additions and 35 deletions
+2 -2
View File
@@ -72,7 +72,7 @@ class SmsController extends BaseController
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'mobile و message الزامی است', 422);
}
$this->smsService->dispatchAsync($mobile, $message, $provider);
$this->smsService->dispatchAsync($mobile, $message, $provider, tag: \App\Sms\Entity\SmsLog::TAG_USER_TEMPLATE);
return $this->success(['message' => 'پیامک در صف ارسال قرار گرفت']);
}
@@ -483,7 +483,7 @@ class SmsController extends BaseController
$message = $template->renderBody($vars);
$this->smsService->dispatchAsync(
$mobile, $message, $provider, $template->getUuid(),
$vars, $template->getProviderCode()
$vars, $template->getProviderCode(), \App\Sms\Entity\SmsLog::TAG_USER_TEMPLATE
);
return $this->success(['message' => 'پیامک در صف ارسال قرار گرفت']);
+109
View File
@@ -0,0 +1,109 @@
<?php
namespace App\Sms\Controller;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
use App\Sms\Entity\SmsMessageTemplate;
use App\Sms\Repository\SmsMessageTemplateRepository;
use OpenApi\Attributes as OA;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
/**
* مدیریت متن ویرایش‌پذیر پیامک‌های سیستمی (بر اساس تگ).
*/
#[OA\Tag(name: 'SMS')]
#[IsGranted('ROLE_ADMIN')]
class SmsMessageController extends BaseController
{
public function __construct(
private readonly SmsMessageTemplateRepository $repo,
) {}
#[OA\Get(
path: '/api/v1/admin/sms/messages',
summary: 'لیست متن‌های سیستمی پیامک (بر اساس تگ)',
security: [['bearerAuth' => []]],
responses: [new OA\Response(response: 200, description: 'لیست متن‌ها')]
)]
#[Route('/api/v1/admin/sms/messages', methods: ['GET'])]
public function list(): JsonResponse
{
$existing = [];
foreach ($this->repo->findAll() as $tpl) {
$existing[$tpl->getTag()] = $tpl->toArray();
}
// تگ‌های پیش‌فرضی که هنوز در DB رکورد ندارند را هم با مقدار پیش‌فرض نشان بده.
$items = [];
foreach (SmsMessageTemplate::DEFAULTS as $tag => $def) {
$items[] = $existing[$tag] ?? [
'tag' => $tag,
'title' => $def['title'],
'body' => $def['body'],
'variables' => $def['variables'],
'updated_at' => null,
];
}
return $this->success(['data' => $items]);
}
#[OA\Patch(
path: '/api/v1/admin/sms/messages/{tag}',
summary: 'ویرایش متن یک پیامک سیستمی',
security: [['bearerAuth' => []]],
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(
required: ['body'],
properties: [new OA\Property(property: 'body', type: 'string')]
)
),
responses: [
new OA\Response(response: 200, description: 'متن به‌روزرسانی شد'),
new OA\Response(response: 404, description: 'تگ ناشناخته'),
new OA\Response(response: 422, description: 'placeholder نامعتبر'),
]
)]
#[Route('/api/v1/admin/sms/messages/{tag}', methods: ['PATCH'])]
public function update(string $tag, Request $request): JsonResponse
{
if (!isset(SmsMessageTemplate::DEFAULTS[$tag])) {
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'تگ پیامک ناشناخته است', 404);
}
$data = json_decode($request->getContent(), true) ?? [];
$body = trim((string) ($data['body'] ?? ''));
if ($body === '') {
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'متن پیامک الزامی است', 422);
}
$allowed = SmsMessageTemplate::DEFAULTS[$tag]['variables'];
preg_match_all('/\{([a-zA-Z0-9_]+)\}/', $body, $m);
$used = array_unique($m[1]);
$unknown = array_diff($used, $allowed);
if (!empty($unknown)) {
return $this->error(
ErrorCodes::ERR_VALIDATION_001,
'placeholder نامعتبر: ' . implode(', ', $unknown) . ' — مجاز: ' . implode(', ', $allowed),
422,
'body',
);
}
$tpl = $this->repo->findByTag($tag);
if ($tpl === null) {
$def = SmsMessageTemplate::DEFAULTS[$tag];
$tpl = new SmsMessageTemplate($tag, $def['title'], $body, $def['variables']);
} else {
$tpl->setBody($body);
}
$this->repo->save($tpl);
return $this->success(['data' => $tpl->toArray()]);
}
}