260706_1_계정, 로그인, 보안 초안 작성
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
"""기술지원 문의 요청 API."""
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
|
||||
from common_util.common_util_auth import get_optional_session
|
||||
from common_util.common_util_email import send_email_background
|
||||
from common_util.common_util_email_templates import support_request_email
|
||||
from config.config_db import get_db_pool
|
||||
from config.config_system import ADMIN_EMAIL
|
||||
|
||||
from .A08_Support_Schema import SupportRequestPayload
|
||||
|
||||
router = APIRouter(prefix="/api/a08", tags=["Support"])
|
||||
|
||||
|
||||
async def _get_company_name(company_id: int | None) -> str | None:
|
||||
"""회사 ID로 회사명을 조회한다. 없으면 None."""
|
||||
if not company_id:
|
||||
return None
|
||||
pool = get_db_pool()
|
||||
async with pool.acquire() as connection, connection.cursor() as cursor:
|
||||
await cursor.execute("SELECT name FROM companies WHERE id = %s", (company_id,))
|
||||
row = await cursor.fetchone()
|
||||
return row[0] if row else None
|
||||
|
||||
|
||||
@router.post("/support")
|
||||
async def submit_support_request(payload: SupportRequestPayload, request: Request):
|
||||
"""기술지원 문의 요청 접수 및 메일 발송.
|
||||
|
||||
로그인 상태면 세션의 회사 정보를 연결하고, 비로그인이면 고객이 입력한 값만 저장한다.
|
||||
"""
|
||||
# 로그인 상태면 회사 정보 연결 (비로그인이면 None)
|
||||
session = await get_optional_session(request)
|
||||
company_id = session["company_id"] if session else None
|
||||
company_name = await _get_company_name(company_id)
|
||||
|
||||
pool = get_db_pool()
|
||||
async with pool.acquire() as connection, connection.cursor() as cursor:
|
||||
await cursor.execute(
|
||||
"""INSERT INTO support_requests
|
||||
(company_id, company_name, user_name, user_email, user_phone, subject, message)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s)""",
|
||||
(
|
||||
company_id,
|
||||
company_name,
|
||||
payload.name,
|
||||
payload.email,
|
||||
payload.phone,
|
||||
payload.subject,
|
||||
payload.message,
|
||||
),
|
||||
)
|
||||
await connection.commit()
|
||||
|
||||
# 관리자에게 문의 알림 메일 발송
|
||||
if ADMIN_EMAIL:
|
||||
subject, html = support_request_email(
|
||||
payload.name,
|
||||
payload.email,
|
||||
payload.phone,
|
||||
company_name,
|
||||
payload.subject,
|
||||
payload.message,
|
||||
)
|
||||
send_email_background(ADMIN_EMAIL, subject, html)
|
||||
|
||||
return {"status": "success", "message": "문의가 접수되었습니다. 곧 연락드리겠습니다."}
|
||||
@@ -0,0 +1,13 @@
|
||||
"""기술지원 문의 요청 데이터 모델."""
|
||||
|
||||
from pydantic import BaseModel, EmailStr, Field
|
||||
|
||||
|
||||
class SupportRequestPayload(BaseModel):
|
||||
"""기술지원 문의 요청"""
|
||||
|
||||
name: str = Field(..., min_length=1, max_length=255, description="작성자 이름")
|
||||
email: EmailStr = Field(..., description="작성자 이메일")
|
||||
phone: str | None = Field(None, max_length=30, description="작성자 연락처")
|
||||
subject: str = Field(..., min_length=1, max_length=255, description="문의 제목")
|
||||
message: str = Field(..., min_length=1, max_length=5000, description="문의 내용")
|
||||
@@ -13,6 +13,7 @@
|
||||
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import { createButton, createInputField } from "@ui/ui_template_elements";
|
||||
import { isBlank, isValidEmail } from "@util/common_util_validate";
|
||||
import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch";
|
||||
import "./A08_Support_UI_Style.css";
|
||||
|
||||
/** locale 헬퍼 */
|
||||
@@ -93,12 +94,19 @@ export function renderA08Support(root: HTMLElement): void {
|
||||
type: "text",
|
||||
required: true,
|
||||
});
|
||||
|
||||
const emailField = createInputField({
|
||||
label: L("A08_Support_Field_Email"),
|
||||
placeholder: L("A08_Support_Field_Email_Placeholder"),
|
||||
type: "email",
|
||||
required: true,
|
||||
});
|
||||
const phoneField = createInputField({
|
||||
label: L("A08_Support_Field_Phone"),
|
||||
placeholder: L("A08_Support_Field_Phone_Placeholder"),
|
||||
type: "tel",
|
||||
required: false,
|
||||
});
|
||||
const subjectField = createInputField({
|
||||
label: L("A08_Support_Field_Subject"),
|
||||
placeholder: L("A08_Support_Field_Subject_Placeholder"),
|
||||
@@ -132,6 +140,7 @@ export function renderA08Support(root: HTMLElement): void {
|
||||
|
||||
const name = nameField.input.value;
|
||||
const email = emailField.input.value;
|
||||
const phone = phoneField.input.value;
|
||||
const subject = subjectField.input.value;
|
||||
const message = messageField.textarea.value;
|
||||
|
||||
@@ -161,16 +170,47 @@ export function renderA08Support(root: HTMLElement): void {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: POST /api/a08/support 연동. 성공 시 폼 초기화 후 성공 메시지 표시.
|
||||
form.reset();
|
||||
messageField.textarea.value = "";
|
||||
successMsg.hidden = false;
|
||||
// API 호출: POST /api/a08/support
|
||||
(async () => {
|
||||
try {
|
||||
const response = await fetch("/api/a08/support", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name, email, phone: phone || null, subject, message }),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const error = (await response.json()) as { detail?: string };
|
||||
throw new Error(error.detail ?? "요청 실패");
|
||||
}
|
||||
form.reset();
|
||||
messageField.textarea.value = "";
|
||||
successMsg.hidden = false;
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : "오류 발생";
|
||||
nameField.setError(msg);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
form.addEventListener("submit", onA08_Support_Submit_Click);
|
||||
form.append(nameField.root, emailField.root, subjectField.root, messageField.root, submitBtn);
|
||||
form.append(
|
||||
nameField.root,
|
||||
emailField.root,
|
||||
phoneField.root,
|
||||
subjectField.root,
|
||||
messageField.root,
|
||||
submitBtn,
|
||||
);
|
||||
|
||||
card.append(title, subtitle, successMsg, form);
|
||||
page.append(card);
|
||||
root.append(page);
|
||||
|
||||
// 백그라운드에서 세션 정보 로드 (페이지 렌더링을 지연시키지 않음)
|
||||
(async () => {
|
||||
const sessionUser = await fetchSessionUser();
|
||||
if (sessionUser?.name) nameField.input.value = sessionUser.name;
|
||||
if (sessionUser?.email) emailField.input.value = sessionUser.email;
|
||||
})();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user