import os, json, re from fastapi import APIRouter, Request from fastapi.responses import JSONResponse from shared import GROQ_API_KEY, NAVER_CLIENT_ID, NAVER_CLIENT_SECRET, _sanitize_text router = APIRouter() @router.post("/api/writer") async def writer_endpoint(request: Request): if not GROQ_API_KEY: return JSONResponse({"error": "GROQ_API_KEY not set"}, status_code=500) try: body = await request.json() except: return JSONResponse({"error": "invalid json"}, status_code=400) style = (body.get("style") or "블로그")[:20] topic = _sanitize_text(body.get("topic") or "")[:1000] context = _sanitize_text(body.get("context") or "")[:3000] tone = (body.get("tone") or "전문적")[:20] if not topic and not context: return JSONResponse({"error": "주제 또는 참고 내용이 필요합니다"}, status_code=400) style_guides = { "블로그": "SEO에 적합한 블로그 포스트. 제목(H1) + 소제목(H2) 구조. 도입-본문-마무리. 키워드 자연스럽게 배치.", "SNS": "인스타그램/페이스북용 짧고 임팩트 있는 글. 이모지 활용. 해시태그 5~10개 포함. 300자 이내.", "이메일": "비즈니스 이메일. 제목줄 + 인사 + 본문 + 마무리. 정중하고 간결하게.", "보도자료": "언론 보도자료 형식. 제목 + 부제 + 리드문(누가/언제/어디서/무엇을/왜) + 본문 + 회사소개.", "광고카피": "짧고 강렬한 광고 카피. 헤드라인 + 서브카피 + CTA. 다양한 버전 3개 제시.", "유튜브대본": "유튜브 영상 대본. 후킹 오프닝 + 본문(타임스탬프 포함) + 아웃트로 + CTA.", } guide = style_guides.get(style, style_guides["블로그"]) prompt = f"""다음 조건으로 글을 작성해줘: - 스타일: {style} - 톤: {tone} - 작성 가이드: {guide} """ if topic: prompt += f"\n- 주제: {topic}\n" if context: prompt += f"\n- 참고 내용:\n{context}\n" prompt += "\n반드시 한국어로 작성. 바로 사용할 수 있는 완성된 글로 출력." import httpx try: async with httpx.AsyncClient(timeout=60.0) as client: resp = await client.post( "https://api.groq.com/openai/v1/chat/completions", headers={"Authorization": f"Bearer {GROQ_API_KEY}", "Content-Type": "application/json"}, json={"model": "openai/gpt-oss-120b", "messages": [{"role": "user", "content": prompt}], "max_completion_tokens": 3000, "temperature": 0.8} ) if resp.status_code != 200: return JSONResponse({"error": f"API error {resp.status_code}"}, status_code=502) rd = resp.json() text = rd.get("choices", [{}])[0].get("message", {}).get("content", "글을 생성하지 못했습니다.") return {"ok": True, "content": text, "style": style} except Exception as e: return JSONResponse({"error": str(e)[:200]}, status_code=500) @router.post("/api/shopping") async def shopping_endpoint(request: Request): if not NAVER_CLIENT_ID or not NAVER_CLIENT_SECRET: return JSONResponse({"error": "NAVER API 키가 설정되지 않았습니다"}, status_code=500) try: body = await request.json() except: return JSONResponse({"error": "invalid json"}, status_code=400) query = _sanitize_text(body.get("query") or "")[:100] sort = body.get("sort", "sim") if not query: return JSONResponse({"error": "검색어가 필요합니다"}, status_code=400) import httpx try: async with httpx.AsyncClient(timeout=15.0) as client: resp = await client.get( "https://openapi.naver.com/v1/search/shop.json", params={"query": query, "display": 20, "sort": sort}, headers={ "X-Naver-Client-Id": NAVER_CLIENT_ID, "X-Naver-Client-Secret": NAVER_CLIENT_SECRET, } ) if resp.status_code != 200: print(f"[shopping] Naver API {resp.status_code}: {resp.text[:200]}") return JSONResponse({"error": f"네이버 API 오류 ({resp.status_code})"}, status_code=502) data = resp.json() items = data.get("items", []) if not items: return {"ok": True, "items": [], "total": 0, "query": query, "tip": "검색 결과가 없습니다."} results = [] for item in items[:20]: title_clean = re.sub(r'', '', item.get("title", "")) results.append({ "title": title_clean, "price": int(item.get("lprice", 0)), "hprice": int(item.get("hprice", 0)) if item.get("hprice") else None, "mall": item.get("mallName", ""), "link": item.get("link", ""), "image": item.get("image", ""), "category": "/".join(filter(None, [ item.get("category1", ""), item.get("category2", ""), item.get("category3", ""), ])), "type": "price_compare" if item.get("productType") == "2" else "single", }) results.sort(key=lambda x: x["price"] if x["price"] > 0 else 99999999) lowest = results[0]["price"] if results else 0 highest = max((r["price"] for r in results), default=0) return { "ok": True, "items": results, "total": data.get("total", len(results)), "query": query, "lowest": lowest, "highest": highest, "sort": sort, } except Exception as e: print(f"[shopping] error: {e}") return JSONResponse({"error": str(e)[:200]}, status_code=500)