160 lines
5.4 KiB
Python
160 lines
5.4 KiB
Python
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)
|