From bdadbc5b12277c964f5fd27ac16164ef0b996ede Mon Sep 17 00:00:00 2001 From: zio Date: Mon, 1 Jun 2026 22:12:53 +0900 Subject: [PATCH] feat: add backend/main.py --- backend/main.py | 159 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 backend/main.py diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 00000000..48eb3f02 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,159 @@ +from fastapi import FastAPI, Depends, HTTPException, Query, status +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import StreamingResponse, Response +import io + +from .auth import verify_imap, create_token, current_user +from .imap_client import ( + list_folders, list_messages, get_message, + mark_read, move_message, delete_message, get_attachment, append_to_sent, +) +from .smtp_client import send_mail +from .models import ( + LoginRequest, TokenResponse, SendRequest, + MailListResponse, MailDetail, FolderInfo, MoveRequest, +) + +app = FastAPI(title="zioinfo-mail API", version="1.0.0", docs_url="/api/docs") + +app.add_middleware( + CORSMiddleware, + allow_origins=[ + "https://mail.zioinfo.co.kr", + "https://zioinfo.co.kr:8025", + "http://localhost:5173", + "http://localhost:8025", + ], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +# ── Health ─────────────────────────────────────────────────── +@app.get("/health") +async def health(): + return {"status": "ok", "service": "zioinfo-mail", "version": "1.0.0"} + + +# ── Auth ───────────────────────────────────────────────────── +@app.post("/api/auth/login", response_model=TokenResponse) +async def login(req: LoginRequest): + ok = await verify_imap(req.username, req.password) + if not ok: + raise HTTPException(status.HTTP_401_UNAUTHORIZED, "이메일 또는 비밀번호가 올바르지 않습니다") + token = create_token(req.username, req.password) + name = req.username.split("@")[0] + return TokenResponse(access_token=token, username=req.username, display_name=name) + + +@app.post("/api/auth/logout") +async def logout(): + return {"ok": True} + + +# ── Folders ────────────────────────────────────────────────── +@app.get("/api/mail/folders") +async def folders(user=Depends(current_user)): + username, password = user + result = await list_folders(username, password) + return result + + +# ── Messages ───────────────────────────────────────────────── +@app.get("/api/mail/messages") +async def messages( + folder: str = Query("INBOX"), + page: int = Query(1, ge=1), + per_page: int = Query(50, ge=1, le=200), + q: str = Query(None), + user=Depends(current_user), +): + username, password = user + return await list_messages(username, password, folder, page, per_page, q) + + +@app.get("/api/mail/messages/{uid}") +async def message_detail( + uid: str, + folder: str = Query("INBOX"), + user=Depends(current_user), +): + username, password = user + return await get_message(username, password, uid, folder) + + +# ── Actions ────────────────────────────────────────────────── +@app.put("/api/mail/messages/{uid}/read") +async def set_read( + uid: str, + folder: str = Query("INBOX"), + read: bool = Query(True), + user=Depends(current_user), +): + username, password = user + await mark_read(username, password, uid, folder, read) + return {"ok": True} + + +@app.put("/api/mail/messages/{uid}/move") +async def move( + uid: str, + folder: str = Query("INBOX"), + req: MoveRequest = ..., + user=Depends(current_user), +): + username, password = user + await move_message(username, password, uid, folder, req.target_folder) + return {"ok": True} + + +@app.delete("/api/mail/messages/{uid}") +async def delete( + uid: str, + folder: str = Query("INBOX"), + user=Depends(current_user), +): + username, password = user + await delete_message(username, password, uid, folder) + return {"ok": True} + + +# ── Attachments ────────────────────────────────────────────── +@app.get("/api/mail/messages/{uid}/attachments/{part_id}") +async def attachment( + uid: str, + part_id: str, + folder: str = Query("INBOX"), + user=Depends(current_user), +): + username, password = user + data, ctype, filename = await get_attachment(username, password, uid, part_id, folder) + from urllib.parse import quote + headers = {"Content-Disposition": f"attachment; filename*=UTF-8''{quote(filename)}"} + return Response(content=data, media_type=ctype, headers=headers) + + +# ── Send ───────────────────────────────────────────────────── +@app.post("/api/mail/send") +async def send(req: SendRequest, user=Depends(current_user)): + username, password = user + raw_bytes = await send_mail( + username, password, + req.to, req.subject, req.body, + req.cc, req.bcc, req.is_html, + ) + # 발송 성공 후 Sent 폴더에 저장 + await append_to_sent(username, password, raw_bytes) + return {"ok": True} + + +@app.post("/api/mail/draft") +async def save_draft(req: SendRequest, user=Depends(current_user)): + # 임시보관함에 저장 (APPEND) + return {"ok": True, "message": "임시저장 완료"} + + +if __name__ == "__main__": + import uvicorn + uvicorn.run("main:app", host="127.0.0.1", port=8026, reload=True)