feat: Implement SMS sending functionality with KavehNegar and Rangineh providers

- Add SendSmsMessage class for encapsulating SMS message data.
- Create KavehNegarProvider and RanginehProvider classes implementing SmsProviderInterface for sending SMS.
- Implement SmsLogRepository and SmsTemplateRepository for managing SMS logs and templates.
- Develop SendSmsHandler for handling SMS sending messages.
- Create SmsService to manage SMS dispatching and logging.
- Add UserProfileController for managing user profiles with CRUD operations.
- Implement UserProfile entity and repository for user profile data management.
- Update symfony.lock and bootstrap.php for project dependencies and environment setup.
This commit is contained in:
hamed
2026-06-09 22:00:34 +03:30
commit de1a78a235
222 changed files with 36388 additions and 0 deletions
+202
View File
@@ -0,0 +1,202 @@
<?php
namespace App\Sms\Controller;
use App\Auth\Entity\User;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
use App\Sms\Entity\SmsTemplate;
use App\Sms\Repository\SmsTemplateRepository;
use App\Sms\Service\SmsService;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('IS_AUTHENTICATED_FULLY')]
class SmsController extends BaseController
{
public function __construct(
private readonly SmsTemplateRepository $templateRepo,
private readonly SmsService $smsService,
) {}
// ── Send SMS directly ─────────────────────────────────────────────────────
#[IsGranted('ROLE_ADMIN')]
#[Route('/api/v1/sms/send', methods: ['POST'])]
public function send(Request $request): JsonResponse
{
$data = json_decode($request->getContent(), true) ?? [];
$mobile = trim($data['mobile'] ?? '');
$message = trim($data['message'] ?? '');
$provider = $data['provider'] ?? 'kavenegar';
if (empty($mobile) || empty($message)) {
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'mobile و message الزامی است', 422);
}
$this->smsService->dispatchAsync($mobile, $message, $provider);
return $this->success(['message' => 'پیامک در صف ارسال قرار گرفت']);
}
// ── Templates ─────────────────────────────────────────────────────────────
#[IsGranted('ROLE_ADMIN')]
#[Route('/api/v1/sms/template', methods: ['POST'])]
public function createTemplate(Request $request): JsonResponse
{
$data = json_decode($request->getContent(), true) ?? [];
$name = trim($data['name'] ?? '');
$body = trim($data['body'] ?? '');
$variables = $data['variables'] ?? [];
if (empty($name) || empty($body)) {
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'name و body الزامی است', 422);
}
$template = new SmsTemplate($name, $body, $variables);
$this->templateRepo->save($template);
return $this->success(['data' => $template->toArray()], 201);
}
#[Route('/api/v1/sms/template/{uuid}', methods: ['GET'])]
public function getTemplate(string $uuid): JsonResponse
{
$template = $this->templateRepo->findByUuid($uuid);
if ($template === null) {
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'تمپلیت یافت نشد', 404);
}
return $this->success(['data' => $template->toArray()]);
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/api/v1/sms/template/{uuid}', methods: ['PATCH'])]
public function updateTemplate(string $uuid, Request $request): JsonResponse
{
$template = $this->templateRepo->findByUuid($uuid);
if ($template === null) {
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'تمپلیت یافت نشد', 404);
}
if ($template->getStatus() === SmsTemplate::STATUS_APPROVED) {
return $this->error(ErrorCodes::ERR_SMS_003, ErrorCodes::message(ErrorCodes::ERR_SMS_003), 422);
}
$data = json_decode($request->getContent(), true) ?? [];
if (array_key_exists('name', $data)) $template->setName($data['name']);
if (array_key_exists('body', $data)) $template->setBody($data['body']);
if (array_key_exists('variables', $data)) $template->setVariables($data['variables']);
$this->templateRepo->save($template);
return $this->success(['data' => $template->toArray()]);
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/api/v1/sms/template/{uuid}/submit', methods: ['POST'])]
public function submitTemplate(string $uuid): JsonResponse
{
$template = $this->templateRepo->findByUuid($uuid);
if ($template === null) {
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'تمپلیت یافت نشد', 404);
}
$template->submitForReview();
$this->templateRepo->save($template);
return $this->success(['data' => $template->toArray()]);
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/api/v1/sms/template/{uuid}', methods: ['DELETE'])]
public function deleteTemplate(string $uuid): JsonResponse
{
$template = $this->templateRepo->findByUuid($uuid);
if ($template === null) {
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'تمپلیت یافت نشد', 404);
}
$this->templateRepo->remove($template);
return $this->success(['message' => 'تمپلیت حذف شد']);
}
// ── Admin moderation ──────────────────────────────────────────────────────
#[IsGranted('ROLE_ADMIN')]
#[Route('/api/v1/admin/sms/templates', methods: ['GET'])]
public function listTemplates(): JsonResponse
{
$templates = array_map(fn(SmsTemplate $t) => $t->toArray(), $this->templateRepo->findAll());
return $this->success(['data' => $templates]);
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/api/v1/admin/sms/template/{uuid}/approve', methods: ['POST'])]
public function approveTemplate(string $uuid, Request $request): JsonResponse
{
$template = $this->templateRepo->findByUuid($uuid);
if ($template === null) {
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'تمپلیت یافت نشد', 404);
}
$data = json_decode($request->getContent(), true) ?? [];
$template->approve($data['note'] ?? null);
if (!empty($data['provider_code'])) $template->setProviderCode($data['provider_code']);
$this->templateRepo->save($template);
return $this->success(['data' => $template->toArray()]);
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/api/v1/admin/sms/template/{uuid}/reject', methods: ['POST'])]
public function rejectTemplate(string $uuid, Request $request): JsonResponse
{
$template = $this->templateRepo->findByUuid($uuid);
if ($template === null) {
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'تمپلیت یافت نشد', 404);
}
$data = json_decode($request->getContent(), true) ?? [];
$note = trim($data['note'] ?? '');
if (empty($note)) {
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'دلیل رد الزامی است', 422);
}
$template->reject($note);
$this->templateRepo->save($template);
return $this->success(['data' => $template->toArray()]);
}
// ── Send via template ─────────────────────────────────────────────────────
#[IsGranted('ROLE_ADMIN')]
#[Route('/api/v1/sms/send-template', methods: ['POST'])]
public function sendViaTemplate(Request $request): JsonResponse
{
$data = json_decode($request->getContent(), true) ?? [];
$mobile = trim($data['mobile'] ?? '');
$templateUuid = trim($data['template_uuid'] ?? '');
$vars = $data['vars'] ?? [];
$provider = $data['provider'] ?? 'kavenegar';
$template = $this->templateRepo->findByUuid($templateUuid);
if ($template === null) {
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'تمپلیت یافت نشد', 404);
}
if ($template->getStatus() !== SmsTemplate::STATUS_APPROVED) {
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'تمپلیت هنوز تأیید نشده است', 422);
}
$message = $template->renderBody($vars);
$this->smsService->dispatchAsync(
$mobile, $message, $provider, $template->getUuid(),
$vars, $template->getProviderCode()
);
return $this->success(['message' => 'پیامک در صف ارسال قرار گرفت']);
}
}