Initial commit - clean project structure
This commit is contained in:
253
config/settings.py
Normal file
253
config/settings.py
Normal file
@@ -0,0 +1,253 @@
|
||||
"""
|
||||
Django settings for config project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 5.2.17.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.2/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/5.2/ref/settings/
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'django-insecure-jn!s*25lz^+bjm101tzmlz8jlj70i0qka++*h-(!0s^^vk-0ss'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
|
||||
# 3rd party
|
||||
'rest_framework',
|
||||
'drf_spectacular',
|
||||
|
||||
# Tadrisino apps (فاز اول - ۴ از ۱۱ اپ)
|
||||
'accounts',
|
||||
'content',
|
||||
'progress',
|
||||
'ai_tutor',
|
||||
|
||||
# Tadrisino apps (فاز دوم - بخش ۱ از ۳: subscription, payment, discount)
|
||||
'subscription',
|
||||
'payment',
|
||||
'discount',
|
||||
|
||||
# Tadrisino apps (فاز دوم - بخش ۲: notifications)
|
||||
'notifications',
|
||||
|
||||
# Tadrisino apps (فاز دوم - بخش ۳: affiliate)
|
||||
'affiliate',
|
||||
|
||||
# Tadrisino apps (فاز دوم - بخش ۴: support)
|
||||
'support',
|
||||
|
||||
# Tadrisino apps (فاز دوم - بخش ۵: family)
|
||||
'family',
|
||||
|
||||
# Tadrisino apps (سیستم پرداخت بازار + آفر/تخفیف + لاگ عمومی تغییر Status)
|
||||
'statuslog',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'config.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'config.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/5.2/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'fa-ir'
|
||||
|
||||
TIME_ZONE = 'Asia/Tehran'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/5.2/howto/static-files/
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Tadrisino — تنظیمات پروژه
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
# این پروژه از مدل User جنگو (auth) استفاده نمیکنه؛ طبق طراحی سینیورت
|
||||
# accounts.User یک مدل مستقل (business model) هست، نه AUTH_USER_MODEL.
|
||||
# احراز هویت با یک TokenAuthentication سفارشی (accounts/authentication.py)
|
||||
# روی همین مدل accounts.User انجام میشه؛ نه سشن/کوکی جنگو.
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||
'accounts.authentication.TadrisinoTokenAuthentication',
|
||||
],
|
||||
'DEFAULT_PERMISSION_CLASSES': [
|
||||
'rest_framework.permissions.AllowAny',
|
||||
],
|
||||
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
|
||||
'DEFAULT_RENDERER_CLASSES': [
|
||||
'rest_framework.renderers.JSONRenderer',
|
||||
'rest_framework.renderers.BrowsableAPIRenderer',
|
||||
],
|
||||
}
|
||||
|
||||
SPECTACULAR_SETTINGS = {
|
||||
'TITLE': 'Tadrisino API',
|
||||
'DESCRIPTION': (
|
||||
'بکاند اپلیکیشن آموزشی Tadrisino — احراز هویت با OTP، محتوای درسی '
|
||||
'و پیشرفت کاربر، دستیار هوش مصنوعی، اشتراک و پرداخت، تخفیف، '
|
||||
'اعلانها، همکاری در فروش، پشتیبانی و حساب خانواده.'
|
||||
),
|
||||
'VERSION': '1.0.0',
|
||||
'SERVE_INCLUDE_SCHEMA': False,
|
||||
'COMPONENT_SPLIT_REQUEST': True,
|
||||
'SWAGGER_UI_SETTINGS': {
|
||||
'deepLinking': True,
|
||||
'displayRequestDuration': True,
|
||||
'defaultModelsExpandDepth': -1,
|
||||
'docExpansion': 'list',
|
||||
'filter': True,
|
||||
'persistAuthorization': True,
|
||||
},
|
||||
}
|
||||
|
||||
# اعتبار توکن دسترسی (ثانیه) و رفرشتوکن (روز)
|
||||
ACCESS_TOKEN_TTL_SECONDS = 3600
|
||||
REFRESH_TOKEN_TTL_DAYS = 30
|
||||
|
||||
# در محیط توسعه/تست، این کد OTP همیشه معتبره تا بدون نیاز به SMS واقعی
|
||||
# بشه لاگین رو با Swagger تست کرد. در پروداکشن حتماً حذف بشه.
|
||||
DEV_MASTER_OTP = '0000'
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Content progress (NodeProgress status)
|
||||
# -----------------------------------------------------------------------
|
||||
# بعد از چند روز بدون فعالیت روی یک Node که status اون in_progress هست،
|
||||
# management command مربوطه (content/management/commands/
|
||||
# mark_abandoned_node_progress.py) اونو abandoned علامت میزنه. ۱۴ روز
|
||||
# (نه ۳-۷ روزِ رایج در اپهای مشابه) عمداً انتخاب شده چون قطعی/محدودیت
|
||||
# اینترنت و دیتای موبایل در ایران باعث وقفههای چندروزهی غیرارادی
|
||||
# میشه که نباید فوراً بهعنوان «رهاکردن درس» حساب بشه.
|
||||
NODE_PROGRESS_ABANDON_DAYS = 14
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Payment security
|
||||
# -----------------------------------------------------------------------
|
||||
# POST /api/payment/orders طبق سند یک فراخوانی سرور-به-سرور از اپ
|
||||
# Subscriptionه (نه از کلاینت). در عمل subscription/views.py مستقیماً
|
||||
# تابع payment.services.create_order رو import و صدا میزنه، پس این
|
||||
# endpoint اصلاً لازم نیست از بیرون صدا زده بشه؛ اما چون در urls.py
|
||||
# رجیستر و AllowAnyه، هر کلاینتی میتونه با یک userId دلخواه سفارش بسازه.
|
||||
# برای بستن این حفره، درخواست باید هدر X-Internal-Key رو برابر همین مقدار
|
||||
# بفرسته؛ در پروداکشن حتماً از env خونده بشه و یک مقدار تصادفی/طولانی باشه.
|
||||
INTERNAL_SERVICE_KEY = os.environ.get('INTERNAL_SERVICE_KEY', 'dev-internal-service-key-change-me')
|
||||
|
||||
# POST /api/payment/webhook قراره از درگاه پرداخت بیاد، نه از یک کلاینت
|
||||
# دلخواه؛ بدون امضا هر کسی میتونه با یک orderId معتبر status=paid بفرسته
|
||||
# و بدون پرداخت واقعی، اشتراک رو فعال کنه. درگاههای واقعی (زرینپال و غیره)
|
||||
# امضای HMAC واقعی خودشون رو دارن؛ اینجا چون درگاه واقعی وصل نیست، بهجای
|
||||
# HMAC یک shared-secret ساده به اسم X-Webhook-Secret استفاده شده — همون
|
||||
# سطح حفاظتی که در سند API برای این endpoint خواسته شده. برای اتصال واقعی
|
||||
# این باید با بررسی امضای HMAC درگاه جایگزین بشه.
|
||||
PAYMENT_WEBHOOK_SECRET = os.environ.get('PAYMENT_WEBHOOK_SECRET', 'dev-webhook-secret-change-me')
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Bazaar In-App Billing (پرداخت درونبرنامهای کافهبازار — REST API v2)
|
||||
# -----------------------------------------------------------------------
|
||||
# طبق داکیومنت رسمی (developers.cafebazaar.ir/fa/guidelines/in-app-billing)،
|
||||
# صحتسنجی پرداخت بازار یک OAuth2 سرور-به-سروره: یک بار با CLIENT_ID/
|
||||
# CLIENT_SECRET و کد بازگشتی از پنل devapi، REFRESH_TOKEN گرفته میشه (یکبار،
|
||||
# دستی)؛ از اون به بعد بکاند با همین REFRESH_TOKEN، ACCESS_TOKEN تازه
|
||||
# میگیره و با اون به endpoint وضعیت خرید/اشتراک درخواست میزنه.
|
||||
# ⚠️ کافهبازار اواخر ۱۴۰۳ روش صحتسنجی رو تغییر داد؛ قبل از اتصال واقعی،
|
||||
# حتماً آخرین مسیر endpointها رو از همون صفحهی رسمی/پنل devapi خودتون
|
||||
# تایید کنید (BAZAAR_API_BASE پایینی رو بر همون اساس ست کنید).
|
||||
BAZAAR_PACKAGE_NAME = os.environ.get('BAZAAR_PACKAGE_NAME', 'ir.tadrisino.app')
|
||||
BAZAAR_CLIENT_ID = os.environ.get('BAZAAR_CLIENT_ID', '')
|
||||
BAZAAR_CLIENT_SECRET = os.environ.get('BAZAAR_CLIENT_SECRET', '')
|
||||
BAZAAR_REFRESH_TOKEN = os.environ.get('BAZAAR_REFRESH_TOKEN', '')
|
||||
BAZAAR_API_BASE = os.environ.get('BAZAAR_API_BASE', 'https://pardakht.cafebazaar.ir/devapi/v2')
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
Reference in New Issue
Block a user