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
+4
View File
@@ -0,0 +1,4 @@
framework:
cache:
app: cache.adapter.redis
default_redis_provider: '%env(REDIS_URL)%'
+5
View File
@@ -0,0 +1,5 @@
when@dev:
debug:
# Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
# See the "server:dump" command to start a new server.
dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"
+45
View File
@@ -0,0 +1,45 @@
doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%'
profiling_collect_backtrace: '%kernel.debug%'
use_savepoints: true
orm:
auto_generate_proxy_classes: true
enable_lazy_ghost_objects: true
report_fields_where_declared: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true
mappings:
App:
type: attribute
is_bundle: false
dir: '%kernel.project_dir%/src'
prefix: 'App'
alias: App
controller_resolver:
auto_mapping: false
when@test:
doctrine:
dbal:
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
when@prod:
doctrine:
orm:
auto_generate_proxy_classes: false
proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies'
query_cache_driver:
type: pool
pool: doctrine.system_cache_pool
result_cache_driver:
type: pool
pool: doctrine.result_cache_pool
framework:
cache:
pools:
doctrine.result_cache_pool:
adapter: cache.app
doctrine.system_cache_pool:
adapter: cache.system
+6
View File
@@ -0,0 +1,6 @@
doctrine_migrations:
migrations_paths:
# namespace is arbitrary but should be different from App\Migrations
# as migrations classes should NOT be autoloaded
'DoctrineMigrations': '%kernel.project_dir%/migrations'
enable_profiler: false
+15
View File
@@ -0,0 +1,15 @@
# see https://symfony.com/doc/current/reference/configuration/framework.html
framework:
secret: '%env(APP_SECRET)%'
# Note that the session will be started ONLY if you read or write from it.
session: true
#esi: true
#fragments: true
when@test:
framework:
test: true
session:
storage_factory_id: session.storage.factory.mock_file
@@ -0,0 +1,5 @@
lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(JWT_PASSPHRASE)%'
token_ttl: 3600
+22
View File
@@ -0,0 +1,22 @@
framework:
messenger:
failure_transport: failed
transports:
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
retry_strategy:
max_retries: 3
delay: 5000
multiplier: 2
failed: 'doctrine://default?queue_name=failed'
sync: 'sync://'
routing:
'App\Shared\Message\SendSmsMessage': async
when@test:
framework:
messenger:
transports:
async: 'in-memory://'
+19
View File
@@ -0,0 +1,19 @@
nelmio_api_doc:
documentation:
info:
title: ClinicPro API
description: مستندات API سیستم کلینیک‌پرو
version: 1.0.0
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
security:
- bearerAuth: []
areas:
path_patterns:
- ^/api
- ^/oauth
- ^/health
+16
View File
@@ -0,0 +1,16 @@
nelmio_cors:
defaults:
origin_regex: true
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
allow_methods: ['GET', 'OPTIONS', 'POST', 'PATCH', 'DELETE']
allow_headers: ['Content-Type', 'Authorization', 'X-CSRF-Token', 'Content-Disposition']
expose_headers: ['X-RateLimit-Limit', 'X-RateLimit-Remaining', 'X-RateLimit-Reset']
max_age: 3600
allow_credentials: false
paths:
'^/api/':
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
'^/oauth/':
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
'^/health':
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
+3
View File
@@ -0,0 +1,3 @@
framework:
property_info:
with_constructor_extractor: true
+13
View File
@@ -0,0 +1,13 @@
framework:
rate_limiter:
# OTP send-code: max 5 requests per hour per IP (prevents SMS flood)
send_code:
policy: 'sliding_window'
limit: 5
interval: '60 minutes'
# Login: max 10 attempts per minute per IP (brute force protection)
login:
policy: 'fixed_window'
limit: 10
interval: '1 minute'
+10
View File
@@ -0,0 +1,10 @@
framework:
router:
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
default_uri: '%env(DEFAULT_URI)%'
when@prod:
framework:
router:
strict_requirements: null
+79
View File
@@ -0,0 +1,79 @@
security:
password_hashers:
App\Auth\Entity\User:
algorithm: auto
providers:
app_user_provider:
entity:
class: App\Auth\Entity\User
property: mobileNumber
firewalls:
dev:
pattern: ^/(_profiler|_wdt|assets|build)/
security: false
health:
pattern: ^/health$
security: false
public_endpoints:
pattern: ^/(api/v1/user/(send-code|verify-code|register)|oauth/token$|session/token|api/v1/categorys/|api/v1/doctors$|api/v1/clinics$|api/v1/clinic/doctor-list/|api/v1/clinic-pro/doctor-addresses/|api/v1/appointment-slots|api/v1/comments/|api/v1/rate/|api/v1/blogs$)
stateless: true
security: false
payment_callback:
pattern: ^/api/v1/(payment|subscription-payment)/callback/
stateless: true
security: false
api:
pattern: ^/(api|oauth)/
stateless: true
provider: app_user_provider
custom_authenticators:
- App\Auth\Security\PasswordAuthenticator
jwt: ~
access_control:
- { path: ^/health$, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/user/send-code, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/user/verify-code, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/user/register, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/user/login, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/appointment-slots, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/comments/, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/rate/, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/blogs$, roles: PUBLIC_ACCESS }
- path: '^/api/v1/blog/[^/]+$'
methods: [GET]
roles: PUBLIC_ACCESS
- { path: ^/oauth/token$, roles: PUBLIC_ACCESS }
- { path: ^/oauth/token/refresh$, roles: PUBLIC_ACCESS }
- { path: ^/session/token, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/payment/callback/, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/subscription-payment/callback/, roles: PUBLIC_ACCESS }
- { path: ^/api/doc, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/categorys/, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/doctors$, roles: PUBLIC_ACCESS }
- path: '^/api/v1/doctor/[^/]+$'
methods: [GET]
roles: PUBLIC_ACCESS
- { path: ^/api/v1/clinic/doctor-list/, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/clinic-pro/doctor-addresses/, roles: PUBLIC_ACCESS }
- { path: ^/api/v1/clinics$, roles: PUBLIC_ACCESS }
- path: '^/api/v1/clinic/[^/]+$'
methods: [GET]
roles: PUBLIC_ACCESS
- { path: ^/api/v1/user/\d+$, methods: [DELETE], roles: ROLE_ADMIN }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
- { path: ^/oauth/userinfo, roles: IS_AUTHENTICATED_FULLY }
- { path: ^/oauth/logout, roles: IS_AUTHENTICATED_FULLY }
when@test:
security:
password_hashers:
App\Auth\Entity\User:
algorithm: auto
cost: 4
+11
View File
@@ -0,0 +1,11 @@
framework:
validation:
# Enables validator auto-mapping support.
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
#auto_mapping:
# App\Entity\: []
when@test:
framework:
validation:
not_compromised_password: false