TOP
본문 바로가기
[파이썬 Projects]/<파이썬 웹개발>

[파이썬] FastAPI - 메모 앱 프로젝트 13: 아이디 찾기

by 기록자_Recordian 2025. 5. 13.
728x90
반응형
이전 내용
 

[파이썬] FastAPI - 메모 앱 프로젝트 12: 파일 분할하기

이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 11: 회원 탈퇴이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 10: 환영 이메일 발송이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 9: 소셜 로그인 추가(네

puppy-foot-it.tistory.com


아이디, 비밀번호 찾기 구현하기

 

웹 페이지 테스트를 위해 계정을 생성하는데, 비밀번호에 정규표현식을 걸어놔서 간단한 비밀번호 생성이 안되다보니 내가 만들어놓고도 금세 까먹는 상황이 자주 생겼다. 그리고 실제로도 오랜만에 사이트에 접속하거나, 자주 사용하지 않는 사이트에 접속 시 계정 정보를 잊는 경우가 있어 이에 대한 기능을 구현해 보는 것도 좋은 학습 기회가 될 거 같아 이를 구현해 보려 한다.

  • 아이디: 가입한 이메일을 입력하면 이메일에 맞는 사용자명을 조회하고, 이를 등록한 이메일로 발송해 주는 기능을 구현
  • 비밀번호: 기존 비밀번호가 아닌 새로운 비밀번호를 입력하도록 하는 기능 구현
  • 최종적으로 home.html에 '아이디/비밀번호 찾기' 를 구현하여 연결되도록 설정

아이디 찾기 구현하기

 

◆ email_class.py

먼저 email_class.py 파일에 새로운 클래스 'EmailServiceFindId' 와 'EmailRequest' 를 추가해 준다.

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from dotenv import load_dotenv
import os

load_dotenv()

class EmailServiceWelcome:
    def send_email(
            self,
            receiver_email: str,
    ):
        sender_email = os.getenv('EMAIL_ADDRESS')
        password = os.getenv('EMAIL_PASSWORD')

        message = MIMEMultipart()
        message["From"] = sender_email
        message["To"] = receiver_email
        message["Subject"] = "회원 가입을 환영합니다."

        body = "메모 앱 서비스를 이용해 주셔서 감사합니다."
        message.attach(MIMEText(body, "plain"))

        with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
            server.login(sender_email, password)
            server.send_message(message)

class EmailServiceBye:
    def send_email(
            self,
            receiver_email: str,
    ):
        sender_email = os.getenv('EMAIL_ADDRESS')
        password = os.getenv('EMAIL_PASSWORD')

        message = MIMEMultipart()
        message["From"] = sender_email
        message["To"] = receiver_email
        message["Subject"] = "탈퇴 완료 이메일."

        body = "그동안 메모 앱 서비스를 이용해 주셔서 감사합니다."
        message.attach(MIMEText(body, "plain"))

        with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
            server.login(sender_email, password)
            server.send_message(message)

class EmailServiceFindId:
    def send_email(
            self,
            receiver_email: str,
            username: str,
    ):
        sender_email = os.getenv('EMAIL_ADDRESS')
        password = os.getenv('EMAIL_PASSWORD')

        message = MIMEMultipart()
        message["From"] = sender_email
        message["To"] = receiver_email
        message["Subject"] = "아이디 찾기 이메일."

        body = f"사용자님의 아이디는 {username} 입니다."
        message.attach(MIMEText(body, "plain"))

        with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
            server.login(sender_email, password)
            server.send_message(message)
            
class EmailRequest(BaseModel):
    email: str

 

◆ email_service.py

그 다음에 email_service.py 에 사용자 이름을 이메일로 전송하는 로직을 추가해 준다.

from service.email_class import EmailServiceWelcome, EmailServiceBye, EmailServiceFindId # 추가

# 사용자 이름 이메일 전송 (사용자 찾기)
def send_username_email(email: str, username: str):
    email_service = EmailServiceFindId() # EmailServiceFindId 클래스 인스턴스 생성
    try:
        email_service.send_email(receiver_email=email, username=username)
        logger.info(f"사용자 이름 정보를 {email}로 전송했습니다.")
    except Exception as e:
        logger.error(f"이메일 전송 실패: {e}") # 에러 로깅

 

◆ users_controller.py

users_controller.py 파일에 아이디를 검색하면 이메일로 발송해 주는 라우터를 생성하여 로직을 구현한다.

from service.email_service import send_bye_email, send_welcome_email, send_username_email # 추가
from service.email_class import EmailRequest # 추가

# 아이디 찾기 (이메일로 발송)
@router.post("/send-username")
async def find_id(req: EmailRequest, db:Session=Depends(get_db)):
    email = req.email
    logger.info(f"사용자 이름 요청: 이메일 {email}")

    user = db.query(User).filter(User.email == email).first()

    if user is None:
        logger.warning(f"사용자 이름 요청 실패: 해당 이메일로 등록된 사용자가 없습니다. 이메일: {email}")
        raise HTTPException(status_code=404, detail="해당 이메일로 등록된 사용자가 없습니다.")
    
    # 사용자 이름 이메일 발송
    try:
        send_username_email(email=email, username=user.username)
        logger.info(f"사용자 이름 {user.username}을 {email}로 성공적으로 전송했습니다.")
    except Exception as e:
        logger.error(f"이메일 전송 실패: {e}")
        raise HTTPException(status_code=500, detail="이메일 전송에 실패했습니다. 다시 시도해 주세요")
    
    return {"success": True, "message": "사용자 이름이 이메일로 발송되었습니다."}

 

◆ home.html

home.html에 '아이디/비밀번호 찾기' 버튼을 추가한다. (회원 가입 밑)

<p>
    <a href="/find_id" style="text-decoration: none; color:blue;">
    아이디/비밀번호 찾기 </a>
</p>

 

◆ find_id.html

그리고 이메일을 입력하여 이메일 발송 버튼을 누를 페이지인 find_id.html 페이지를 생성하는데, 기존의 home.html의 스타일과 비슷하도록 만든다.

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>아이디/비밀번호 찾기</title>
    <style>
        body {
            font-family: 'Noto Sans KR', sans-serif;
            background-color: #f8f9fa;
            margin: 0;
            padding: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            text-align: center;
        }
        .container {
            max-width: 400px;
            padding: 2rem;
            background-color: #fff;
            border-radius: 10px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
            margin: 1rem;
            width: 100%;
        }
        h1 {
            font-size: 1.5rem;
            color: #007bff;
            margin-bottom: 2rem;
        }
        .form-group {
            margin-bottom: 1rem;
            width: 100%;
        }
        .form-group label {
            margin-bottom: .5rem;
            color: #888;
            text-align: left;
            display: block;
        }
        .form-group input {
            padding: 0.75rem;
            border: 1px solid #ced4da;
            border-radius: 5px;
            width: 100%;
            box-sizing: border-box;
        }
        .form-group input:focus {
            border-color: #80bdff;
            box-shadow: 0 0 0 2px rgba(0,123,255,.25);
        }
        .buttons button {
            width: 100%;
            padding: 0.75rem;
            border: none;
            border-radius: 5px;
            background-color: #007bff;
            color: white;
            margin-top: 0.5rem;
            font-size: 1rem;
            font-weight: 600;
            cursor: pointer;
            box-sizing: border-box;
        }
        .buttons button:hover {
            background-color: #0056b3;
        }
        #message {
            margin-top: 1rem;
            color: #28a745;
            font-weight: 500;
        }
        @media (max-width: 768px) {
            .container {
                width: 90%;
                padding: 1.5rem;
            }
            h1 {
                font-size: 1.25rem;
            }
        }
    </style>
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700&display=swap" rel="stylesheet">
</head>
<body>
    <div class="container">
        <h1>아이디/비밀번호 찾기</h1>

        <form id="find-id-form" onsubmit="return submitForm(event)">
            <div class="form-group">
                <label for="email">이메일 주소</label>
                <input type="email" id="email" name="email" required>
            </div>
            <div class="buttons">
                <button type="submit">아이디 전송</button>
            </div>
        </form>

        <div id="message"></div>
    </div>

    <script>
        function submitForm(event) {
            event.preventDefault();  // 기본 제출 이벤트 방지

            const email = document.getElementById('email').value;
            const messageDiv = document.getElementById('message');

            fetch('/send-username', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ email: email })
            })
            .then(response => {
                if (!response.ok) {
                    return response.json().then(err => {
                        throw new Error(err.detail || '이메일 전송에 실패했습니다.');
                    });
                }
                return response.json();
            })
            .then(data => {
                messageDiv.innerText = data.message;
                messageDiv.style.color = '#28a745'; // 성공 메시지 색상
            })
            .catch(error => {
                messageDiv.innerText = error.message;
                messageDiv.style.color = '#dc3545'; // 에러 메시지 색상
            });
        }
    </script>
</body>
</html>

 

◆ main.py

main.py 파일에도 아이디/비밀번호 찾기 페이지 엔드포인트를 추가해 준다.

import logging
from fastapi.responses import HTMLResponse

# 아이디 비밀번호 찾기 페이지 엔드포인트 추가
@app.get("/find_id", response_class=HTMLResponse)
async def read_find_id(request: Request):
    logger.info("아이디/비밀번호 찾기 페이지 요청 수신")
    return templates.TemplateResponse('find_id.html', {"request": request})

 


아이디 찾기 테스트

 

main.py를 실행하여 서버를 실행한 뒤, 메일로 아이디가 잘 오는지 확인해 보기 위해 이메일 주소가 유효한 것으로 새로운 계정을 생성한다.

그리고 하단에 뜨는 아이디/비밀번호 찾기 링크를 클릭하면 아래와 같이 /find_id 로 잘 넘어간다. 

 

앞서 생성한 계정의 이메일 주소를 입력한 후, [아이디 전송] 버튼을 누르면 사용자 이름이 이메일로 발송되었다는 메시지가 뜨고,

 

이메일이 실제로 잘 발송된 것을 확인할 수 있다.

로그를 확인해 보니, 에러없이 잘 작동한다.


다음 내용

 

[파이썬] FastAPI - 메모 앱 프로젝트 14: 비밀번호 찾기(수정)

이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 13: 아이디 찾기이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 12: 파일 분할하기이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 11: 회원 탈퇴이전 내용 [

puppy-foot-it.tistory.com

728x90
반응형