2026-09-15 09:16:20 +03:30
2026-09-15 09:16:20 +03:30
2026-09-04 13:05:54 +03:30
2026-09-15 09:09:16 +03:30
2026-08-31 15:11:52 +03:30
2026-08-24 15:54:08 +03:30
2026-08-24 15:54:08 +03:30
2026-08-24 15:54:08 +03:30
2026-08-24 15:54:08 +03:30
2026-08-24 15:54:08 +03:30
2026-09-04 13:05:54 +03:30
2026-08-24 15:54:08 +03:30
2026-08-24 15:54:08 +03:30
2026-09-04 13:05:54 +03:30
2026-09-04 13:05:54 +03:30

پیامک‌بان — بک‌اند

بک‌اند 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>/.

راه‌اندازی

  1. یک دیتابیس PostgreSQL از قبل آماده کن (این پروژه فقط Dockerfile دارد، نه docker-compose — یعنی پستگری را خودت جدا بالا می‌آوری یا از یک سرویس موجود استفاده می‌کنی).

  2. فایل env بساز:

    cp .env.example .env
    

    و مقداردهی کن — نکته‌های مهم:

    • SECRET_KEY: python -c "import secrets; print(secrets.token_urlsafe(50))"
    • DB_*: اطلاعات همان پستگری که در مرحله‌ی ۱ آماده کردی
    • SMS_PATTERN_NAME و SMS_TOKEN_URL: طبق پایین توضیح داده شده
  3. ساخت ایمیج و اجرا:

    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
S
Description
No description provided
Readme 697 KiB
Languages
Python 87.3%
HTML 8.6%
CSS 3.7%
Dockerfile 0.3%