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
@@ -0,0 +1,84 @@
<?php
namespace App\Shared\Service;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Exception\AppException;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class FileValidatorService
{
private const ALLOWED_SIGNATURES = [
'image/jpeg' => ["\xFF\xD8\xFF"],
'image/png' => ["\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"],
'image/webp' => ["RIFF"],
];
private const ALLOWED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp'];
public function __construct(private readonly int $maxSizeBytes = 5_242_880) {}
public function validateUploadedFile(UploadedFile $file): string
{
if ($file->getSize() > $this->maxSizeBytes) {
throw new AppException(ErrorCodes::ERR_FILE_002, null, 422);
}
$binaryContent = (string) file_get_contents($file->getPathname());
return $this->validate($binaryContent, $file->getClientOriginalName());
}
public function validate(string $binaryContent, string $claimedFilename): string
{
if (strlen($binaryContent) > $this->maxSizeBytes) {
throw new AppException(ErrorCodes::ERR_FILE_002, null, 422);
}
$detected = false;
foreach (self::ALLOWED_SIGNATURES as $signatures) {
foreach ($signatures as $sig) {
if (str_starts_with($binaryContent, $sig)) {
$detected = true;
break 2;
}
}
}
if (!$detected) {
throw new AppException(ErrorCodes::ERR_FILE_001, null, 422);
}
return $this->sanitizeFilename($claimedFilename);
}
public function sanitizeFilename(string $filename): string
{
$safeName = preg_replace('/[^a-zA-Z0-9._-]/', '', basename($filename));
if (empty($safeName) || str_contains($safeName, '..')) {
throw new AppException(ErrorCodes::ERR_FILE_001, null, 422);
}
$ext = strtolower(pathinfo($safeName, PATHINFO_EXTENSION));
if (!in_array($ext, self::ALLOWED_EXTENSIONS, true)) {
throw new AppException(ErrorCodes::ERR_FILE_001, null, 422);
}
return $safeName;
}
public function detectMimeType(string $filePath): string
{
$handle = fopen($filePath, 'rb');
$header = fread($handle, 12);
fclose($handle);
foreach (self::ALLOWED_SIGNATURES as $mime => $signatures) {
foreach ($signatures as $sig) {
if (str_starts_with($header, $sig)) {
return $mime;
}
}
}
throw new AppException(ErrorCodes::ERR_FILE_001, 'نوع فایل پشتیبانی نمی‌شود', 422);
}
}