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)