پیامکبان — بکاند
بکاند DRF برای اپ پیامکبان: احراز هویت با شماره موبایل + کد یکبارمصرف (OTP) و توکن JWT، بهعلاوه یک اندپوینت پشتیبانگیری تا مخاطبین/فیلدهای دلخواه/ گروهها/قالبهای پیام کاربر با تعویض گوشی از دست نرود.
پشتهی فنی
- Django 5.1 + Django REST Framework
- djangorestframework-simplejwt (JWT، با blacklist برای logout واقعی)
- PostgreSQL
- کد تایید (OTP) با HMAC-SHA256 هش میشود و هرگز متن ساده در دیتابیس نیست
— دقیقا مثل الگویی که در
apps/accountپروژهی فروشگاه استفاده شده، چون این ماژول پلاگین توکار ندارد؛ بهجای پکیجdjango-otp(که برای دستگاههای TOTP/Authenticator ساخته شده، نه ارسال پیامک با پترن)، همان الگوی OTP دستی و امتحانپسدادهی پروژهی مرجع را پیاده کردم. اگر واقعا خودِ پکیجdjango-otpرا میخواهی، بگو تا جایگزینش کنم.
ساختار
apps/account/ احراز هویت (User با شماره موبایل، OTP، JWT)
apps/banker/ مخاطبین، فیلدهای دلخواه per-group، گروهها، قالبهای پیام (CRUD + بکاپ)
apps/messaging/ ارسال گروهی چندکاناله (پیامک/واتساپ/تلگرام/بله/ایتا)
apps/bizapps/ ریزاپهای کسبوکار کوچک: مشتریان، دفتر درآمد/هزینه، انبار، نوبتدهی
config/ تنظیمات، urls، wsgi/asgi
apps/bizapps — ریزاپهای کسبوکار کوچک
چهار مدل مستقل، هرکدام CRUD سادهی خودش را دارد (مثل الگوی apps/banker:
id همان UUID تولیدشده در اپ فلاتر، رکورد به کاربر صاحبش محدود):
| مسیر | مدل |
|---|---|
/api/bizapps/customers/ |
Customer — مشتریان |
/api/bizapps/ledger/ |
LedgerEntry — دفتر درآمد و هزینه (entry_type: income/expense) |
/api/bizapps/products/ |
Product — انبار (quantity, low_stock_threshold) |
/api/bizapps/appointments/ |
Appointment — نوبتدهی (status: scheduled/done/cancelled) |
هرکدام GET/POST روی مسیر بالا و GET/PATCH/DELETE روی <id>/.
راهاندازی
-
یک دیتابیس PostgreSQL از قبل آماده کن (این پروژه فقط Dockerfile دارد، نه docker-compose — یعنی پستگری را خودت جدا بالا میآوری یا از یک سرویس موجود استفاده میکنی).
-
فایل env بساز:
cp .env.example .envو مقداردهی کن — نکتههای مهم:
SECRET_KEY:python -c "import secrets; print(secrets.token_urlsafe(50))"DB_*: اطلاعات همان پستگری که در مرحلهی ۱ آماده کردیSMS_PATTERN_NAMEوSMS_TOKEN_URL: طبق پایین توضیح داده شده
-
ساخت ایمیج و اجرا:
docker build -t sms-banker-backend . docker run -d --name sms-banker-backend \ --env-file .env \ -p 8000:8000 \ sms-banker-backendاگر پستگری هم داخل داکر است (کانتینر جدا، بدون compose)، هر دو کانتینر را در یک شبکهی مشترک بگذار تا
DB_HOSTبا نام کانتینر پستگری کار کند:docker network create sms-banker-net docker run -d --name sms-banker-db --network sms-banker-net \ -e POSTGRES_DB=sms_banker -e POSTGRES_USER=... -e POSTGRES_PASSWORD=... \ postgres:16 docker run -d --name sms-banker-backend --network sms-banker-net \ --env-file .env -p 8000:8000 sms-banker-backend # و در .env: DB_HOST=sms-banker-dbورود/خروج، migrate و collectstatic خودکار در
entrypoint.shهنگام استارت کانتینر انجام میشود.
پیامک OTP — فقط دو مقدار از .env
طبق خواستهات، ماژول ارسال پیامک (apps/account/send_sms.py) فقط دو مقدار
را از .env میخواند:
SMS_PATTERN_NAME: نام/کد قالبی که نزد سرویسدهندهی پیامکت ثبت شدهSMS_TOKEN_URL: آدرسی که یک درخواستPOSTبا بدنهی{"pattern": ..., "receptor": "09xxxxxxxxx", "token": "12345"}برایش فرستاده میشود
اگر ساختار واقعی سرویسدهندهات با این payload فرق دارد (مثلا نیاز به
هدر Authorization یا شکل دیگری از پارامترها دارد)، فقط تابع
_call_token_url در همان فایل را با مستندات سرویسدهندهات هماهنگ کن؛
بقیهی پروژه فقط send_otp_message(phone, code) را صدا میزند و از این
جزئیات خبر ندارد.
اندپوینتها
apps/account — احراز هویت
| متد | مسیر | توضیح |
|---|---|---|
| POST | /api/account/auth/request-otp/ |
ارسال کد تایید (کاربر جدید هم خودکار ساخته میشود) |
| POST | /api/account/auth/verify-otp/ |
تایید کد، دریافت access/refresh |
| POST | /api/account/auth/token/refresh/ |
گرفتن access token جدید از refresh token |
| POST | /api/account/auth/logout/ |
باطل کردن refresh token (نیاز به access token) |
| GET | /api/account/auth/profile/ |
دریافت پروفایل |
| PATCH | /api/account/auth/profile/ |
ویرایش پروفایل (فعلا فقط name) |
| DELETE | /api/account/auth/profile/delete/ |
حذف حساب — نیاز به تایید OTP در بدنهی درخواست |
apps/banker — پشتیبانگیری دادههای اپ
| متد | مسیر | توضیح |
|---|---|---|
| GET | /api/banker/backup/ |
آخرین نسخهی ذخیرهشده (برای بازیابی روی گوشی جدید) |
| POST | /api/banker/backup/ |
جایگزینی کامل دادهی کاربر با آنچه از اپ فرستاده میشود |
هر دو، نیاز به هدر Authorization: Bearer <access token> دارند. بدنهی
POST این شکلی است:
{
"contact_fields": [{"id": "...", "group_id": "...", "label": "شماره گذرنامه", "sort_order": 0}],
"contacts": [{"id": "...", "name": "...", "phone": "...", "device_contact_id": null, "field_values": {"<field_id>": "پشمک"}, "channel_ids": {"telegram": "123456"}}],
"groups": [{"id": "...", "name": "تور", "color": 4283215696, "is_general": false, "member_ids": ["..."]}],
"templates": [{"id": "...", "group_id": null, "name": "...", "body": "سلام {نام}", "mode": "merge"}]
}
idها همان UUID تولیدشده در خودِ اپ فلاتر هستند — منبع حقیقت خودِ اپ روی
گوشی است، این اندپوینت فقط بکاپ/ریستور میکند، نه sync دوطرفهی هوشمند.
فیلدهای دلخواه و قالبها اکنون گروهمحوراند: هر ContactField به یک
ContactGroup تعلق دارد (مثلا گروه «تور» → فیلد «شماره گذرنامه»)، و هر
MessageTemplate یا به یک گروه تعلق دارد یا group_id: null (قالب عمومی —
فقط فیلدهای پایهی نام/شماره). هر کاربر دقیقاً یک گروه با is_general: true
دارد که خودکار ساخته میشود و قابل حذف نیست.
CRUD تکی (علاوه بر بکاپ بالا)
برای عملیات تکی (نه جایگزینی کامل)، بهجای backup:
| متد | مسیر |
|---|---|
| GET/POST | /api/banker/groups/ |
| GET/PATCH/DELETE | /api/banker/groups/<id>/ |
| GET/POST | /api/banker/groups/<group_id>/fields/ |
| GET/PATCH/DELETE | /api/banker/fields/<id>/ |
| GET/POST | /api/banker/contacts/ |
| GET/PATCH/DELETE | /api/banker/contacts/<id>/ |
| GET/POST | /api/banker/templates/ |
| GET/PATCH/DELETE | /api/banker/templates/<id>/ |
apps/messaging — ارسال گروهی چندکاناله
فقط از مسیرهای رسمی هر پلتفرم استفاده میشود: پیامک بالک از طریق کاوهنگار
(خودکار، سمت سرور)، واتساپ از طریق WhatsApp Business Cloud API رسمی،
تلگرام/بله/ایتا از طریق Bot API رسمی — فقط برای مخاطبینی که قبلا opt-in
کردهاند (چت با بات را شروع کردهاند، Contact.channel_ids). هر کانالی که
پیکربندی/opt-in نداشته باشد، بهجای خطا یا دور زدن ضدهرزنامهی پلتفرم،
needs_manual برمیگرداند تا اپ یک صف deep-link یکلمسی به کاربر نشان دهد.
| متد | مسیر | توضیح |
|---|---|---|
| POST | /api/messaging/send/ |
ساخت + dispatch فوری یک ارسال گروهی |
| GET | /api/messaging/<id>/ |
وضعیت/گزارش یک ارسال گروهی |
| POST | /api/messaging/<bulk_id>/confirm/<recipient_id>/ |
تایید ارسال دستی (بعد از تپ کاربر در واتساپ/تلگرام/بله/ایتا) |
بدنهی POST /send/:
{
"group_id": "...",
"contact_ids": [],
"template_id": "...",
"body_override": "",
"channels": ["sms", "whatsapp", "telegram", "bale", "eitaa"]
}
group_id خالی = همهی مخاطبین کاربر. یا template_id بده یا body_override
(متن آزاد بدون قالب). env varهای مربوط به هر کانال در .env.example
مستندند؛ تا وقتی تنظیم نشوند، همان کانال فقط needs_manual برمیگرداند.
نکتهی مهم — بدون Postgres واقعی تست شده
با manage.py check و manage.py makemigrations (بدون اتصال واقعی به
Postgres) تایید شد که مدلها/اپها درست بار میشوند و مهاجرتهای اولیه
(0001_initial) برای هر سه اپ ساخته شدند — اینها الان در ریپو هستند، نیازی
به makemigrations دوباره نیست مگر مدلی عوض شود. در همین بررسی یک باگ
از قبل موجود هم پیدا و رفع شد: ContactGroup.user بدون related_name
صریح با فیلد groups توکار Django (PermissionsMixin) تداخل داشت؛ الان
related_name="contact_groups" است.
قبل از اجرای واقعی روی سیستم خودت با یک Postgres واقعی:
pip install -r requirements.txt
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver