พัฒนา Backend ฟังก์ชัน ลืมรหัสผ่าน? (Forgot Password)

This commit is contained in:
Flook 2025-11-15 23:19:36 +07:00
parent 248a31b13f
commit 606008db88
6 changed files with 166 additions and 12 deletions

View File

@ -21,5 +21,5 @@ COPY . /app/
EXPOSE 8000
# 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"]

20
backend/Dockerfile.celery Normal file
View 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"]

View 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
View 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()

View File

@ -13,9 +13,11 @@ https://docs.djangoproject.com/en/5.2/ref/settings/
from pathlib import Path
import os
# ใน core/settings.py (ด้านบนสุด)
from dotenv import load_dotenv
load_dotenv() # โหลดตัวแปรจาก .env
try:
from dotenv import load_dotenv
load_dotenv() # โหลดตัวแปรจาก .env ใน Local Dev
except ImportError:
pass # ไม่ทำอะไรถ้า Module ไม่พบ (หมายความว่ารันอยู่ใน Docker)
DB_HOST = os.getenv("DB_HOST", "cockroach-1")
@ -52,6 +54,7 @@ THIRD_PARTY_APPS = [
'corsheaders',
'drf_spectacular',
'drf_spectacular_sidecar',
'djcelery_email',
]
LOCAL_APPS = [
@ -206,12 +209,18 @@ REST_FRAMEWORK = {
}
# 3. ตั้งค่า DJOSER (เพื่อจัดการ Auth Endpoints)
DOMAIN = "localhost:5173"
SITE_NAME = 'localhost:5173' # หรือชื่อ Domain จริง
DJOSER = {
# ใช้งาน JWT โดยตรง
'USER_ID_FIELD': 'id', # ใช้ ID ของ User Model
'PASSWORD_RESET_CONFIRM_URL': '#/password/reset/confirm/{uid}/{token}', # URL สำหรับ Frontend
'USERNAME_RESET_CONFIRM_URL': '#/username/reset/confirm/{uid}/{token}',
'ACTIVATION_URL': '#/activate/{uid}/{token}', # หากต้องการยืนยันอีเมล
'EMAIL': {
# ชี้ไปยัง Custom Email Class ที่เราสร้าง
'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': {
'user_create': 'accounts.serializers.UserCreateSerializer', # จะสร้างในขั้นตอนถัดไป
'user': 'accounts.serializers.UserSerializer', # ใช้ UserSerializer เดิม
@ -220,7 +229,10 @@ DJOSER = {
'TOKEN_MODEL': None, # ไม่ใช้ TokenAuth แบบเก่า
'PERMISSIONS': {
'user_create': ['rest_framework.permissions.AllowAny'],
}
},
"DOMAIN": "localhost:5173",
"SITE_NAME": "localhost:5173",
"PROTOCOL": "http",
}
REDIS_HOST = os.getenv("REDIS_HOST", "redis")
@ -248,8 +260,8 @@ SESSION_CACHE_ALIAS = "default"
# CACHE_MIDDLEWARE_KEY_PREFIX = 'auth_cache'
# CELERY CONFIGURATION
CELERY_BROKER_URL = f'redis://{REDIS_HOST}:{REDIS_PORT}/0'
CELERY_RESULT_BACKEND = f'redis://{REDIS_HOST}:{REDIS_PORT}/0'
CELERY_BROKER_URL = os.getenv('CELERY_BROKER_URL', 'redis://redis:6379/0')
CELERY_RESULT_BACKEND = os.getenv('CELERY_RESULT_BACKEND', 'redis://redis:6379/0')
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
@ -310,4 +322,26 @@ SPECTACULAR_SETTINGS = {
'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

View File

@ -13,4 +13,7 @@ celery # ตัว Worker
boto3
python-dotenv
drf-spectacular
drf-spectacular-sidecar
drf-spectacular-sidecar
django-celery-email
python-dotenv
flower