- Implemented GET /api/v1/dashboard/clinic to return clinic stats and today's schedule for clinic owners. - Implemented GET /api/v1/dashboard/doctor to return doctor's stats and today's schedule for doctors. - Implemented GET /api/v1/dashboard/secretary to return stats and conditional appointments for secretaries. feat(migrations): create user_active_context and mobile_verification_otp tables - Added migration to create user_active_context table for tracking active user sessions. - Added migration to create mobile_verification_otp table for handling mobile number verification. feat(migrations): create site_config table for application settings - Added migration to create site_config table to store various site configuration settings. feat(appointments): create MyAppointmentsController for user-specific appointments - Added MyAppointmentsController to handle fetching user-specific appointments with pagination and filtering. feat(auth): implement NotificationMobileController for mobile number verification - Added NotificationMobileController to handle OTP requests and verification for mobile number changes. feat(auth): create MobileVerificationOtp entity for OTP management - Created MobileVerificationOtp entity to manage OTP records for mobile verification. feat(auth): create UserActiveContext entity for user session management - Created UserActiveContext entity to manage user active sessions. feat(config): implement SiteConfigController for managing site settings - Added SiteConfigController to handle fetching and updating site configuration settings. feat(config): create SiteConfig entity and repository for configuration management - Created SiteConfig entity and repository to manage site configuration data.
11 KiB
Authentication API
Prefix:
/api/v1/userand/oauth
Permission: All endpoints in this module are PUBLIC (no JWT required) exceptuserinfoandlogout
POST /api/v1/user/send-code
Send OTP code to mobile number.
Permission: PUBLIC
Request Body
{
"mobile": "09123456789"
}
| Field | Type | Required | Validation |
|---|---|---|---|
mobile |
string | ✅ | Format: 09XXXXXXXXX (11 digits) |
Response 200
{
"success": true,
"data": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"message": "کد تأیید ارسال شد"
}
}
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_VALIDATION_001 |
422 | Invalid mobile format |
ERR_AUTH_004 |
429 | OTP rate limit exceeded |
POST /api/v1/user/verify-code
Verify OTP code. Returns whether this is a new or existing user.
Permission: PUBLIC
Request Body
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"code": "123456"
}
| Field | Type | Required | Description |
|---|---|---|---|
uuid |
string | ✅ | UUID returned from send-code |
code |
string | ✅ | 6-digit OTP |
Response 200
{
"success": true,
"data": {
"message": "کد تأیید شد",
"is_new_user": false,
"uuid": "550e8400-e29b-41d4-a716-446655440000"
}
}
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_AUTH_002 |
401 | Invalid OTP code |
ERR_AUTH_003 |
401 | OTP expired |
ERR_VALIDATION_002 |
422 | Missing required field |
POST /api/v1/user/register
Complete registration for new users (called only when is_new_user: true).
Permission: PUBLIC
Request Body
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"real_name": "علی احمدی"
}
| Field | Type | Required | Description |
|---|---|---|---|
uuid |
string | ✅ | Verified UUID from verify-code |
real_name |
string | ❌ | User's full name |
Response 201
{
"success": true,
"data": {
"message": "ثبتنام با موفقیت انجام شد",
"uuid": "550e8400-e29b-41d4-a716-446655440000"
}
}
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_VALIDATION_002 |
422 | Missing uuid |
ERR_CONFLICT_001 |
409 | User already registered |
POST /api/v1/user/login
Login with mobile number and password (for users who set a password).
Permission: PUBLIC
Request Body
{
"mobile_number": "09123456789",
"password": "mypassword"
}
| Field | Type | Required |
|---|---|---|
mobile_number |
string | ✅ |
password |
string | ✅ |
Response 200
{
"success": true,
"data": {
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
"refresh_token": "def50200..."
}
}
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_AUTH_005 |
401 | Wrong credentials |
ERR_AUTH_006 |
403 | Account suspended |
ERR_AUTH_004 |
429 | Too many attempts |
POST /oauth/token
Exchange verified UUID for JWT access token.
Permission: PUBLIC
Request Body
{
"grant_type": "mobile",
"uuid": "550e8400-e29b-41d4-a716-446655440000"
}
| Field | Type | Required | Description |
|---|---|---|---|
grant_type |
string | ✅ | Must be "mobile" |
uuid |
string | ✅ | UUID from verified OTP flow |
Response 200
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
"refresh_token": "def50200..."
}
JWT payload:
{ username: mobile_number, roles: [...], iat, exp }
Access token TTL: 1 hour | Refresh token TTL: 30 days (stored in Redis)
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_AUTH_002 |
400 | Invalid or expired UUID |
POST /oauth/token/refresh
Refresh expired JWT using refresh token.
Permission: PUBLIC
Request Body
{
"refresh_token": "def50200..."
}
Response 200
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
"refresh_token": "def50200..."
}
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_AUTH_001 |
401 | Invalid or expired refresh token |
GET /oauth/userinfo
Get authenticated user info — extended with multi-context support.
Permission: AUTH — requires valid JWT
Headers
Authorization: Bearer <token>
Response 200
{
"success": true,
"data": {
"id": 4766,
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"mobile_number": "09123456789",
"realName": "دکتر وحید درویشی",
"status": 1,
"roles": ["ROLE_USER", "ROLE_DOCTOR"],
"primary_role": "doctor",
"db_uuid": "a6ef5d29-38b8-4e69-b1ef-27a304696966",
"db_key": "hmac-sha256-hash...",
"context": {
"type": "doctor",
"db_uuid": "a6ef5d29-38b8-4e69-b1ef-27a304696966",
"name": "مطب شخصی دکتر وحید درویشی",
"role": "doctor"
},
"available_contexts": [
{
"type": "doctor",
"db_uuid": "a6ef5d29-38b8-4e69-b1ef-27a304696966",
"name": "مطب شخصی دکتر وحید درویشی",
"role": "doctor"
},
{
"type": "clinic",
"db_uuid": "clinic-uuid-...",
"name": "کلینیک سلامت",
"role": "doctor"
}
]
}
}
| فیلد | نوع | توضیح |
|---|---|---|
primary_role |
string | نقش اصلی: admin | clinic | doctor | secretary | user |
db_uuid |
string|null | UUID موجودیت فعال (null = هنوز context انتخاب نشده) |
db_key |
string|null | HMAC-SHA256(db_uuid, APP_SECRET) برای اعتبارسنجی |
context |
object|null | context فعال انتخابشده |
available_contexts |
array | همه محیطهای کاری قابل انتخاب |
قانون primary_role (اولویتبندی):
ROLE_ADMIN→"admin"ROLE_CLINIC→"clinic"ROLE_DOCTOR→"doctor"ROLE_SECRETARY→"secretary"- بقیه →
"user"
قانون db_uuid:
- اگر یک context وجود دارد: خودکار فعال میشود
- اگر چند context وجود دارد و کاربر هنوز انتخاب نکرده:
null— frontend باید صفحه انتخاب نشان دهد - پس از
POST /api/v1/auth/switch-context: برابر context انتخابشده
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_AUTH_001 |
401 | Invalid or missing token |
POST /api/v1/auth/switch-context
تغییر محیط کاری فعال — باید از لیست available_contexts انتخاب شود.
Permission: AUTH
Request Body
{
"db_uuid": "clinic-uuid-..."
}
| فیلد | نوع | Required | توضیح |
|---|---|---|---|
db_uuid |
string (UUID) | ✅ | UUID محیط کاری از لیست available_contexts |
Response 200
{
"success": true,
"data": {
"db_uuid": "clinic-uuid-...",
"db_key": "new-hmac-hash...",
"context": {
"type": "clinic",
"db_uuid": "clinic-uuid-...",
"name": "کلینیک سلامت",
"role": "doctor"
}
}
}
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_AUTH_001 |
401 | Missing or invalid token |
ERR_AUTH_006 |
403 | db_uuid در لیست context های این کاربر نیست |
ERR_VALIDATION_001 |
422 | db_uuid ارسال نشده |
Notification Mobile (OTP)
Endpoints for setting/verifying a separate SMS notification number for doctors and clinics. This number receives appointment SMS notifications instead of the account login mobile.
Permission: All 4 endpoints require
IS_AUTHENTICATED_FULLY(JWT).
POST /api/v1/notification-mobile/request-otp
Request an OTP code to verify a new notification mobile number.
Permission: AUTH
Request Body
{
"target": "doctor",
"new_mobile": "09123456789"
}
| Field | Type | Required | Validation |
|---|---|---|---|
target |
string | ✅ | doctor or clinic |
new_mobile |
string | ✅ | Format: 09XXXXXXXXX |
Response 200
{
"success": true,
"data": {
"message": "کد تأیید ارسال شد",
"expires_in": 300
}
}
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_VALIDATION_001 |
422 | Invalid target or mobile format |
ERR_NOT_FOUND_001 |
404 | No doctor/clinic profile found for this user |
Notes
- Previous unused OTPs for the entity are deleted before creating a new one
- OTP is 6-digit, expires in 5 minutes
- Sends via SMS asynchronously
POST /api/v1/notification-mobile/verify
Verify the OTP and save the notification mobile number.
Permission: AUTH
Request Body
{
"target": "doctor",
"otp_code": "123456"
}
| Field | Type | Required |
|---|---|---|
target |
string | ✅ doctor or clinic |
otp_code |
string | ✅ 6-digit code |
Response 200
{
"success": true,
"data": {
"notification_mobile": "09123456789",
"message": "شماره اعلان با موفقیت ذخیره شد"
}
}
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_VALIDATION_001 |
422 | Invalid target / OTP expired / wrong code |
ERR_NOT_FOUND_001 |
404 | No OTP request found or profile not found |
GET /api/v1/notification-mobile/{target}
Get the current notification mobile for the authenticated user.
Permission: AUTH
Path param: target — doctor or clinic
Response 200
{
"success": true,
"data": {
"notification_mobile": "09123456789"
}
}
- Returns
nullfornotification_mobileif not set
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_NOT_FOUND_001 |
404 | No profile found for this user |
DELETE /api/v1/notification-mobile/{target}
Remove the notification mobile number.
Permission: AUTH
Path param: target — doctor or clinic
Response 200
{
"success": true,
"data": {
"message": "شماره اعلان حذف شد"
}
}
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_NOT_FOUND_001 |
404 | No profile found for this user |
POST /oauth/logout
Invalidate the refresh token (stored in Redis).
Permission: AUTH
Request Body
{
"refresh_token": "def50200..."
}
| Field | Type | Required |
|---|---|---|
refresh_token |
string | ❌ |
Response 200
{
"success": true,
"data": {
"message": "با موفقیت خارج شدید"
}
}