import os import logging from contextlib import asynccontextmanager from fastapi import FastAPI, Depends, HTTPException from fastapi.middleware.cors import CORSMiddleware from .db import ( init_db, get_public_data, get_profile, update_profile, get_careers, create_career, update_career, delete_career, get_projects, create_project, update_project, delete_project, get_skills, create_skill, update_skill, delete_skill, get_introductions, create_introduction, update_introduction, delete_introduction, set_main_introduction, ) from .models import ( ProfileUpdate, CareerCreate, CareerUpdate, ProjectCreate, ProjectUpdate, SkillCreate, SkillUpdate, IntroCreate, IntroUpdate, AuthRequest, ) from .auth import authenticate, require_auth logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(name)s] %(levelname)s %(message)s") logger = logging.getLogger("portfolio") @asynccontextmanager async def lifespan(app: FastAPI): init_db() logger.info("portfolio service 시작") yield 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", "Authorization"], ) @app.get("/health") def health(): return {"status": "ok"} # ── Public ── @app.get("/api/profile/public") def api_public(): return get_public_data() # ── Auth ── @app.post("/api/profile/auth") def api_auth(body: AuthRequest): return authenticate(body.password) # ── Profile (편집) ── @app.get("/api/profile/profile", dependencies=[Depends(require_auth)]) def api_profile_get(): return get_profile() @app.put("/api/profile/profile", dependencies=[Depends(require_auth)]) def api_profile_update(body: ProfileUpdate): return update_profile(body.model_dump(exclude_none=True)) # ── Careers (편집) ── @app.get("/api/profile/careers", dependencies=[Depends(require_auth)]) def api_careers_list(): return get_careers() @app.post("/api/profile/careers", status_code=201, dependencies=[Depends(require_auth)]) def api_career_create(body: CareerCreate): return create_career(body.model_dump()) @app.put("/api/profile/careers/{career_id}", dependencies=[Depends(require_auth)]) def api_career_update(career_id: int, body: CareerUpdate): result = update_career(career_id, body.model_dump(exclude_none=True)) if not result: raise HTTPException(status_code=404, detail="Career not found") return result @app.delete("/api/profile/careers/{career_id}", dependencies=[Depends(require_auth)]) def api_career_delete(career_id: int): if not delete_career(career_id): raise HTTPException(status_code=404, detail="Career not found") return {"ok": True} # ── Projects (편집) ── @app.get("/api/profile/projects", dependencies=[Depends(require_auth)]) def api_projects_list(): return get_projects() @app.post("/api/profile/projects", status_code=201, dependencies=[Depends(require_auth)]) def api_project_create(body: ProjectCreate): return create_project(body.model_dump()) @app.put("/api/profile/projects/{project_id}", dependencies=[Depends(require_auth)]) def api_project_update(project_id: int, body: ProjectUpdate): result = update_project(project_id, body.model_dump(exclude_none=True)) if not result: raise HTTPException(status_code=404, detail="Project not found") return result @app.delete("/api/profile/projects/{project_id}", dependencies=[Depends(require_auth)]) def api_project_delete(project_id: int): if not delete_project(project_id): raise HTTPException(status_code=404, detail="Project not found") return {"ok": True} # ── Skills (편집) ── @app.get("/api/profile/skills", dependencies=[Depends(require_auth)]) def api_skills_list(): return get_skills() @app.post("/api/profile/skills", status_code=201, dependencies=[Depends(require_auth)]) def api_skill_create(body: SkillCreate): return create_skill(body.model_dump()) @app.put("/api/profile/skills/{skill_id}", dependencies=[Depends(require_auth)]) def api_skill_update(skill_id: int, body: SkillUpdate): result = update_skill(skill_id, body.model_dump(exclude_none=True)) if not result: raise HTTPException(status_code=404, detail="Skill not found") return result @app.delete("/api/profile/skills/{skill_id}", dependencies=[Depends(require_auth)]) def api_skill_delete(skill_id: int): if not delete_skill(skill_id): raise HTTPException(status_code=404, detail="Skill not found") return {"ok": True} # ── Introductions (편집) ── @app.get("/api/profile/introductions", dependencies=[Depends(require_auth)]) def api_intros_list(): return get_introductions() @app.post("/api/profile/introductions", status_code=201, dependencies=[Depends(require_auth)]) def api_intro_create(body: IntroCreate): return create_introduction(body.model_dump()) @app.put("/api/profile/introductions/{intro_id}", dependencies=[Depends(require_auth)]) def api_intro_update(intro_id: int, body: IntroUpdate): result = update_introduction(intro_id, body.model_dump(exclude_none=True)) if not result: raise HTTPException(status_code=404, detail="Introduction not found") return result @app.delete("/api/profile/introductions/{intro_id}", dependencies=[Depends(require_auth)]) def api_intro_delete(intro_id: int): if not delete_introduction(intro_id): raise HTTPException(status_code=404, detail="Introduction not found") return {"ok": True} @app.patch("/api/profile/introductions/{intro_id}/main", dependencies=[Depends(require_auth)]) def api_intro_set_main(intro_id: int): result = set_main_introduction(intro_id) if not result: raise HTTPException(status_code=404, detail="Introduction not found") return result