Files
web-page-backend/realestate-lab/app/main.py

164 lines
5.5 KiB
Python

import os
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI, Query, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from apscheduler.schedulers.background import BackgroundScheduler
from .db import (
init_db, get_announcements, get_announcement, create_announcement,
update_announcement, delete_announcement, update_all_statuses,
get_profile, upsert_profile, get_matches, mark_match_read,
get_last_collect_log, get_dashboard,
)
from .collector import collect_all
from .matcher import run_matching
from .models import AnnouncementCreate, AnnouncementUpdate, ProfileUpdate
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(name)s] %(levelname)s %(message)s")
logger = logging.getLogger("realestate-lab")
scheduler = BackgroundScheduler(timezone=os.getenv("TZ", "Asia/Seoul"))
def scheduled_collect():
"""매일 09:00 — 수집 + 매칭"""
logger.info("스케줄 수집 시작")
collect_all()
run_matching()
logger.info("스케줄 수집 + 매칭 완료")
def scheduled_status_update():
"""매일 00:00 — 상태 갱신 + 재매칭"""
logger.info("상태 갱신 시작")
update_all_statuses()
run_matching()
logger.info("상태 갱신 + 재매칭 완료")
@asynccontextmanager
async def lifespan(app: FastAPI):
init_db()
scheduler.add_job(scheduled_collect, "cron", hour=9, minute=0, id="collect")
scheduler.add_job(scheduled_status_update, "cron", hour=0, minute=0, id="status_update")
scheduler.start()
logger.info("realestate-lab 시작")
yield
scheduler.shutdown()
app = FastAPI(lifespan=lifespan)
_cors_origins = os.getenv("CORS_ALLOW_ORIGINS", "http://localhost:3007,http://localhost:8080").split(",")
app.add_middleware(
CORSMiddleware,
allow_origins=[o.strip() for o in _cors_origins],
allow_credentials=False,
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allow_headers=["Content-Type"],
)
@app.get("/health")
def health():
return {"status": "ok"}
# ── 공고 API ─────────────────────────────────────────────────────────────────
@app.get("/api/realestate/announcements")
def api_announcements(
region: str = None,
status: str = None,
house_type: str = None,
matched_only: bool = False,
sort: str = "date",
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
):
return get_announcements(region, status, house_type, matched_only, sort, page, size)
@app.get("/api/realestate/announcements/{ann_id}")
def api_announcement_detail(ann_id: int):
ann = get_announcement(ann_id)
if not ann:
raise HTTPException(status_code=404, detail="Announcement not found")
return ann
@app.post("/api/realestate/announcements", status_code=201)
def api_announcement_create(body: AnnouncementCreate):
return create_announcement(body.model_dump())
@app.put("/api/realestate/announcements/{ann_id}")
def api_announcement_update(ann_id: int, body: AnnouncementUpdate):
updated = update_announcement(ann_id, body.model_dump(exclude_none=True))
if not updated:
raise HTTPException(status_code=404, detail="Announcement not found")
return updated
@app.delete("/api/realestate/announcements/{ann_id}")
def api_announcement_delete(ann_id: int):
if not delete_announcement(ann_id):
raise HTTPException(status_code=404, detail="Announcement not found")
return {"ok": True}
# ── 수집 API ─────────────────────────────────────────────────────────────────
@app.post("/api/realestate/collect")
def api_collect():
result = collect_all()
run_matching()
return result
@app.get("/api/realestate/collect/status")
def api_collect_status():
log = get_last_collect_log()
return log if log else {"collected_at": None, "new_count": 0, "total_count": 0, "error": None}
# ── 프로필 API ───────────────────────────────────────────────────────────────
@app.get("/api/realestate/profile")
def api_profile_get():
profile = get_profile()
return profile if profile else {}
@app.put("/api/realestate/profile")
def api_profile_update(body: ProfileUpdate):
return upsert_profile(body.model_dump(exclude_none=True))
# ── 매칭 API ─────────────────────────────────────────────────────────────────
@app.get("/api/realestate/matches")
def api_matches(page: int = Query(1, ge=1), size: int = Query(20, ge=1, le=100)):
return get_matches(page, size)
@app.post("/api/realestate/matches/refresh")
def api_matches_refresh():
run_matching()
return {"ok": True}
@app.patch("/api/realestate/matches/{match_id}/read")
def api_match_read(match_id: int):
if not mark_match_read(match_id):
raise HTTPException(status_code=404, detail="Match not found")
return {"ok": True}
# ── 대시보드 API ─────────────────────────────────────────────────────────────
@app.get("/api/realestate/dashboard")
def api_dashboard():
return get_dashboard()