พัฒนา Backend ฟังก์ชัน ลืมรหัสผ่าน? (Forgot Password)
This commit is contained in:
parent
248a31b13f
commit
606008db88
@ -21,5 +21,5 @@ COPY . /app/
|
|||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
# 7. ENTRYPOINT/CMD: กำหนด Entrypoint หลัก
|
# 7. ENTRYPOINT/CMD: กำหนด Entrypoint หลัก
|
||||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||||
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
|
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
|
||||||
20
backend/Dockerfile.celery
Normal file
20
backend/Dockerfile.celery
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
gcc \
|
||||||
|
libpq-dev \
|
||||||
|
libffi-dev \
|
||||||
|
libjpeg62-turbo-dev \
|
||||||
|
zlib1g-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
CMD ["celery", "-A", "core", "worker", "-l", "info"]
|
||||||
81
backend/accounts/emails.py
Normal file
81
backend/accounts/emails.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
from djoser import email
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.mail import EmailMultiAlternatives
|
||||||
|
|
||||||
|
class CustomPasswordResetEmail(email.PasswordResetEmail):
|
||||||
|
|
||||||
|
template_name = None
|
||||||
|
|
||||||
|
def render(self, context=None):
|
||||||
|
|
||||||
|
context = self.get_context_data()
|
||||||
|
url = context['url']
|
||||||
|
user = context['user']
|
||||||
|
|
||||||
|
self.html_content = self.create_email_html(url, user)
|
||||||
|
self.plain_content = self.create_email_plain(url, user)
|
||||||
|
|
||||||
|
def get_context_data(self):
|
||||||
|
return super().get_context_data()
|
||||||
|
|
||||||
|
def get_subject(self):
|
||||||
|
"""
|
||||||
|
subject ถูกสร้างโดย Djoser ใน context['subject']
|
||||||
|
"""
|
||||||
|
context = self.get_context_data()
|
||||||
|
return context.get("subject", "Password Reset")
|
||||||
|
|
||||||
|
def send(self, to, *args, **kwargs):
|
||||||
|
|
||||||
|
self.render()
|
||||||
|
|
||||||
|
msg = EmailMultiAlternatives(
|
||||||
|
subject=self.get_subject(),
|
||||||
|
body=self.plain_content,
|
||||||
|
from_email=self.from_email,
|
||||||
|
to=to
|
||||||
|
)
|
||||||
|
if self.html_content:
|
||||||
|
msg.attach_alternative(self.html_content, "text/html")
|
||||||
|
|
||||||
|
# djcelery_email จะ Intercep เมธอด send() ของ EmailMultiAlternatives โดยอัตโนมัติ
|
||||||
|
return msg.send()
|
||||||
|
|
||||||
|
def create_email_html(self, url, user):
|
||||||
|
# ดึง context data อีกครั้งเพื่อเข้าถึง protocol และ domain
|
||||||
|
context = self.get_context_data()
|
||||||
|
full_reset_url = f"{context['protocol']}://{context['domain']}{url}" # สร้าง Full URL
|
||||||
|
|
||||||
|
return f"""
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<p>สวัสดีคุณ <b>{user.username}</b>,</p>
|
||||||
|
<p>คุณได้ร้องขอการรีเซ็ตรหัสผ่าน โปรดคลิกลิงก์ด้านล่างเพื่อตั้งรหัสผ่านใหม่:</p>
|
||||||
|
|
||||||
|
<p><a href="{full_reset_url}" style="background-color: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">รีเซ็ตรหัสผ่าน (คลิกที่นี่)</a></p>
|
||||||
|
|
||||||
|
<p>หากลิงก์ไม่สามารถคลิกได้ กรุณาคัดลอกลิงก์นี้ไปวางในเบราว์เซอร์: <b>{full_reset_url}</b></p>
|
||||||
|
<p>หากคุณไม่ได้ร้องขอ โปรดเพิกเฉยต่ออีเมลนี้</p>
|
||||||
|
<p>ขอบคุณครับ,<br/>ทีม DDO Console</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
def create_email_plain(self, url, user):
|
||||||
|
# ดึง context data อีกครั้งเพื่อเข้าถึง protocol และ domain
|
||||||
|
context = self.get_context_data()
|
||||||
|
full_reset_url = f"{context['protocol']}://{context['domain']}{url}" # ⬅️ สร้าง Full URL
|
||||||
|
|
||||||
|
return f"""
|
||||||
|
สวัสดี {user.username},
|
||||||
|
|
||||||
|
คุณได้ร้องขอการรีเซ็ตรหัสผ่าน กรุณาใช้ลิงก์ด้านล่างเพื่อตั้งรหัสผ่านใหม่:
|
||||||
|
|
||||||
|
{full_reset_url}
|
||||||
|
|
||||||
|
หากคุณไม่ได้ร้องขอ โปรดเพิกเฉยต่ออีเมลนี้
|
||||||
|
|
||||||
|
ขอบคุณครับ,
|
||||||
|
ทีม DDO Console
|
||||||
|
"""
|
||||||
16
backend/core/celery.py
Normal file
16
backend/core/celery.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import os
|
||||||
|
from celery import Celery
|
||||||
|
|
||||||
|
# กำหนดค่า Django settings module ให้ Celery
|
||||||
|
# 'core.settings' คือ path ของ settings.py ของโปรเจกต์ Django
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')
|
||||||
|
|
||||||
|
# สร้าง Celery application instance
|
||||||
|
app = Celery('core') # ชื่อตรงนี้ต้องตรงกับ -A core และ CELERY_APP: core
|
||||||
|
|
||||||
|
# โหลด configuration จากไฟล์ settings.py ของ Django
|
||||||
|
# โดย Celery จะใช้ prefix CELERY_ (เช่น CELERY_BROKER_URL)
|
||||||
|
app.config_from_object('django.conf:settings', namespace='CELERY')
|
||||||
|
|
||||||
|
# ค้นหา tasks ทั้งหมดใน INSTALLED_APPS ของ Django โดยอัตโนมัติ
|
||||||
|
app.autodiscover_tasks()
|
||||||
@ -13,9 +13,11 @@ https://docs.djangoproject.com/en/5.2/ref/settings/
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# ใน core/settings.py (ด้านบนสุด)
|
try:
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
load_dotenv() # โหลดตัวแปรจาก .env
|
load_dotenv() # โหลดตัวแปรจาก .env ใน Local Dev
|
||||||
|
except ImportError:
|
||||||
|
pass # ไม่ทำอะไรถ้า Module ไม่พบ (หมายความว่ารันอยู่ใน Docker)
|
||||||
|
|
||||||
DB_HOST = os.getenv("DB_HOST", "cockroach-1")
|
DB_HOST = os.getenv("DB_HOST", "cockroach-1")
|
||||||
|
|
||||||
@ -52,6 +54,7 @@ THIRD_PARTY_APPS = [
|
|||||||
'corsheaders',
|
'corsheaders',
|
||||||
'drf_spectacular',
|
'drf_spectacular',
|
||||||
'drf_spectacular_sidecar',
|
'drf_spectacular_sidecar',
|
||||||
|
'djcelery_email',
|
||||||
]
|
]
|
||||||
|
|
||||||
LOCAL_APPS = [
|
LOCAL_APPS = [
|
||||||
@ -206,12 +209,18 @@ REST_FRAMEWORK = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# 3. ตั้งค่า DJOSER (เพื่อจัดการ Auth Endpoints)
|
# 3. ตั้งค่า DJOSER (เพื่อจัดการ Auth Endpoints)
|
||||||
|
DOMAIN = "localhost:5173"
|
||||||
|
SITE_NAME = 'localhost:5173' # หรือชื่อ Domain จริง
|
||||||
DJOSER = {
|
DJOSER = {
|
||||||
# ใช้งาน JWT โดยตรง
|
# ใช้งาน JWT โดยตรง
|
||||||
'USER_ID_FIELD': 'id', # ใช้ ID ของ User Model
|
'USER_ID_FIELD': 'id', # ใช้ ID ของ User Model
|
||||||
'PASSWORD_RESET_CONFIRM_URL': '#/password/reset/confirm/{uid}/{token}', # URL สำหรับ Frontend
|
'EMAIL': {
|
||||||
'USERNAME_RESET_CONFIRM_URL': '#/username/reset/confirm/{uid}/{token}',
|
# ชี้ไปยัง Custom Email Class ที่เราสร้าง
|
||||||
'ACTIVATION_URL': '#/activate/{uid}/{token}', # หากต้องการยืนยันอีเมล
|
'password_reset': 'accounts.emails.CustomPasswordResetEmail',
|
||||||
|
},
|
||||||
|
'PASSWORD_RESET_CONFIRM_URL': '/password/reset/confirm/{uid}/{token}', # URL ที่ Djoser จะใช้สร้างลิงก์ในอีเมล (ชี้ไปยัง Frontend Route)
|
||||||
|
'USERNAME_RESET_CONFIRM_URL': '/username/reset/confirm/{uid}/{token}',
|
||||||
|
'ACTIVATION_URL': '/activate/{uid}/{token}', # หากต้องการยืนยันอีเมล
|
||||||
'SERIALIZERS': {
|
'SERIALIZERS': {
|
||||||
'user_create': 'accounts.serializers.UserCreateSerializer', # จะสร้างในขั้นตอนถัดไป
|
'user_create': 'accounts.serializers.UserCreateSerializer', # จะสร้างในขั้นตอนถัดไป
|
||||||
'user': 'accounts.serializers.UserSerializer', # ใช้ UserSerializer เดิม
|
'user': 'accounts.serializers.UserSerializer', # ใช้ UserSerializer เดิม
|
||||||
@ -220,7 +229,10 @@ DJOSER = {
|
|||||||
'TOKEN_MODEL': None, # ไม่ใช้ TokenAuth แบบเก่า
|
'TOKEN_MODEL': None, # ไม่ใช้ TokenAuth แบบเก่า
|
||||||
'PERMISSIONS': {
|
'PERMISSIONS': {
|
||||||
'user_create': ['rest_framework.permissions.AllowAny'],
|
'user_create': ['rest_framework.permissions.AllowAny'],
|
||||||
}
|
},
|
||||||
|
"DOMAIN": "localhost:5173",
|
||||||
|
"SITE_NAME": "localhost:5173",
|
||||||
|
"PROTOCOL": "http",
|
||||||
}
|
}
|
||||||
|
|
||||||
REDIS_HOST = os.getenv("REDIS_HOST", "redis")
|
REDIS_HOST = os.getenv("REDIS_HOST", "redis")
|
||||||
@ -248,8 +260,8 @@ SESSION_CACHE_ALIAS = "default"
|
|||||||
# CACHE_MIDDLEWARE_KEY_PREFIX = 'auth_cache'
|
# CACHE_MIDDLEWARE_KEY_PREFIX = 'auth_cache'
|
||||||
|
|
||||||
# CELERY CONFIGURATION
|
# CELERY CONFIGURATION
|
||||||
CELERY_BROKER_URL = f'redis://{REDIS_HOST}:{REDIS_PORT}/0'
|
CELERY_BROKER_URL = os.getenv('CELERY_BROKER_URL', 'redis://redis:6379/0')
|
||||||
CELERY_RESULT_BACKEND = f'redis://{REDIS_HOST}:{REDIS_PORT}/0'
|
CELERY_RESULT_BACKEND = os.getenv('CELERY_RESULT_BACKEND', 'redis://redis:6379/0')
|
||||||
CELERY_ACCEPT_CONTENT = ['json']
|
CELERY_ACCEPT_CONTENT = ['json']
|
||||||
CELERY_TASK_SERIALIZER = 'json'
|
CELERY_TASK_SERIALIZER = 'json'
|
||||||
CELERY_RESULT_SERIALIZER = 'json'
|
CELERY_RESULT_SERIALIZER = 'json'
|
||||||
@ -310,4 +322,26 @@ SPECTACULAR_SETTINGS = {
|
|||||||
'core.spectacular_hooks.rename_djoser_tags',
|
'core.spectacular_hooks.rename_djoser_tags',
|
||||||
],
|
],
|
||||||
# ตั้งค่าอื่น ๆ (ถ้าจำเป็น)
|
# ตั้งค่าอื่น ๆ (ถ้าจำเป็น)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# CELERY EMAIL CONFIGURATION (Transactional Emails)
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
# 1. EMAIL_BACKEND หลัก ใช้ Celery Email Backend
|
||||||
|
# Django จะส่งอีเมลทั้งหมดไปเข้าคิว Celery
|
||||||
|
EMAIL_BACKEND = 'djcelery_email.backends.CeleryEmailBackend'
|
||||||
|
|
||||||
|
# 2. CELERY_EMAIL_BACKEND: กำหนด SMTP ที่ Celery Worker จะใช้
|
||||||
|
# Celery Worker จะใช้ตัวนี้ในการส่งอีเมลออกจริง ๆ
|
||||||
|
CELERY_EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||||
|
|
||||||
|
# Mailjet SMTP Configuration (ใช้ Env Vars)
|
||||||
|
#EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||||
|
EMAIL_HOST = os.getenv('MAILJET_SMTP_HOST')
|
||||||
|
EMAIL_PORT = os.getenv('MAILJET_SMTP_PORT')
|
||||||
|
EMAIL_USE_TLS = os.getenv('MAILJET_SMTP_TLS', 'True') == 'True'
|
||||||
|
EMAIL_HOST_USER = os.getenv('MAILJET_API_KEY') # API Key เป็น Username
|
||||||
|
EMAIL_HOST_PASSWORD = os.getenv('MAILJET_SECRET_KEY') # Secret Key เป็น Password
|
||||||
|
DEFAULT_FROM_EMAIL = os.getenv('DEFAULT_FROM_EMAIL') # อีเมลผู้ส่ง
|
||||||
|
SERVER_EMAIL = DEFAULT_FROM_EMAIL # อีเมลสำหรับแจ้งเตือน Server
|
||||||
@ -13,4 +13,7 @@ celery # ตัว Worker
|
|||||||
boto3
|
boto3
|
||||||
python-dotenv
|
python-dotenv
|
||||||
drf-spectacular
|
drf-spectacular
|
||||||
drf-spectacular-sidecar
|
drf-spectacular-sidecar
|
||||||
|
django-celery-email
|
||||||
|
python-dotenv
|
||||||
|
flower
|
||||||
Loading…
x
Reference in New Issue
Block a user