feat: add backend/main.py
This commit is contained in:
parent
ce3f55ccdf
commit
bdadbc5b12
159
backend/main.py
Normal file
159
backend/main.py
Normal file
@ -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)
|
||||
Loading…
Reference in New Issue
Block a user