{"openapi":"3.0.0","info":{"title":"Pack-IT API 명세서","version":"1.0.0","description":"Pack-IT 백엔드 API 문서입니다."},"servers":[{"url":"http://54.180.159.211:8080","description":"배포 서버"},{"url":"http://localhost:8080","description":"로컬 테스트 서버"}],"paths":{"/api/auth/kakao/callback":{"get":{"summary":"카카오 연동 로그인 콜백","description":"카카오 서버로부터 발급받은 인가 코드를 전달받아 유저를 MEMBER로 승격시키고 리다이렉트합니다.","tags":["Auth"],"parameters":[{"in":"query","name":"code","required":true,"schema":{"type":"string"},"description":"카카오 인가 코드"},{"in":"query","name":"state","required":true,"schema":{"type":"string"},"description":"임시 유저의 nanoId"}],"responses":{"302":{"description":"로그인 성공 및 리다이렉트"},"400":{"description":"필수 데이터 누락"},"500":{"description":"서버 내부 오류"}}}},"/api/letters/ai/generate":{"post":{"operationId":"generateAiLetterContent","summary":"AI 편지 문구 생성","description":"사용자가 작성한 편지 초안과 선택한 카테고리, 톤을 기반으로 AI가 다듬어진 편지 문구를 생성하여 반환합니다. 인증 미들웨어가 적용되어 있으며, 토큰이 없을 경우 게스트 계정을 자동 생성하고 쿠키(accessToken)를 발급합니다.\n","tags":["Letters"],"security":[{"cookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["category","tone","draft_content"],"properties":{"category":{"type":"string","description":"편지 카테고리 (예: 생일, 축하, 감사 등)","example":"생일"},"tone":{"type":"string","description":"다듬을 톤 (예: 다정하게, 격식있게, 감성적인, 담백하게)","example":"다정하게"},"draft_content":{"type":"string","description":"사용자가 작성한 편지 초안","example":"지수야 생일 축하해 오늘 즐거운 시간 보내고 맛있는 것도 많이 먹고 좋은 하루를 보내길 바래"}}}}}},"responses":{"200":{"description":"AI 문구 생성 성공","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","properties":{"ai_content":{"type":"string","example":"사랑하는 지수야, 생일 진심으로 축하해! \n\n일 년 중 가장 소중하고 기쁜 오늘, 네가 누구보다 행복한 시간 보냈으면 좋겠어. 맛있는 음식도 많이 먹고, 네 주변이 온통 기분 좋은 일들로만 가득 채워지는 따뜻한 하루가 되길 바랄게. \n\n항상 밝게 웃는 네 모습이 정말 보기 좋아. 오늘만큼은 세상에서 가장 행복한 주인공이 되어 마음껏 누리는 즐거운 하루 보내길 바라. 다시 한번 생일 축하해!"}}},"meta":{"type":"object","nullable":true,"example":null},"error":{"type":"object","nullable":true}}}}}},"400":{"description":"잘못된 요청 (필수 값 누락 등)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"data":{"type":"object","nullable":true,"example":null},"meta":{"type":"object","nullable":true,"example":null},"error":{"type":"string","example":"잘못된 요청입니다"}}}}}},"401":{"description":"인증 실패 (토큰 만료 또는 인증 정보 없음)","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"에러 메시지","example":"인증이 만료되었습니다. 다시 시도해주세요."}}}}}},"500":{"description":"서버 에러 (AI 생성 실패)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"data":{"type":"object","nullable":true,"example":null},"meta":{"type":"object","nullable":true,"example":null},"error":{"type":"string","example":"AI 문구 생성에 실패했습니다."}}}}}}}}},"/api/letters":{"post":{"operationId":"createLetter","summary":"편지 최종 저장 및 링크 생성 api","description":"최종 편지 데이터를 DB에 저장하고 고유 ID를 반환합니다. 발신자 식별을 위해 인증 미들웨어가 적용됩니다.\n","tags":["Letters"],"security":[{"cookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sender_name","receiver_name","category","content","theme"],"properties":{"sender_id":{"type":"integer","nullable":true,"description":"로그인한 사용자의 경우 유저 고유 ID (비회원은 null)","example":1},"sender_name":{"type":"string","description":"보내는 사람 이름","example":"정화"},"receiver_name":{"type":"string","description":"받는 사람 이름","example":"민수"},"category":{"type":"string","description":"편지 카테고리","example":"생일"},"content":{"type":"string","description":"최종 편지 내용 (AI가 다듬어준 문구 포함)","example":"민수야, 생일 정말 축하해! 맛있는 거 많이 먹어!."},"theme":{"type":"number","description":"선택한 편지 테마 번호 (1~5)","example":1},"password":{"type":"string","nullable":true,"description":"발신자가 설정한 비밀번호","example":"1111"}}}}}},"responses":{"201":{"description":"편지 저장 및 링크 생성 성공","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","properties":{"letter_id":{"type":"string","description":"공유 링크로 사용될 고유 nanoid","example":"V1StG_K9_D9W9SpgS8"},"published_at":{"type":"string","format":"date-time","description":"편지 생성 일시","example":"2026-04-28T14:41:59Z"}}},"meta":{"type":"object","nullable":true,"example":null},"error":{"type":"object","nullable":true,"example":null}}}}}},"400":{"description":"잘못된 요청 (필수 데이터 누락 등)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"data":{"type":"object","nullable":true,"example":null},"meta":{"type":"object","nullable":true,"example":null},"error":{"type":"string","example":"잘못된 요청입니다"}}}}}},"401":{"description":"인증 실패 (토큰 만료 또는 인증 정보 없음)","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"에러 메시지","example":"인증이 만료되었습니다. 다시 시도해주세요."}}}}}},"500":{"description":"서버 에러 (DB 저장 실패 등)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"data":{"type":"object","nullable":true,"example":null},"meta":{"type":"object","nullable":true,"example":null},"error":{"type":"string","example":"편지 저장에 실패했습니다."}}}}}}}}},"/api/letters/{letter_id}":{"get":{"operationId":"getLetterDetail","summary":"편지 상세 조회 (수신자용)","description":"링크를 통해 편지 상세 내용을 조회합니다. 비밀번호 설정 여부는 `/api/letters/{letter_id}/check-password` API를 통해 먼저 확인하세요.\n","tags":["Letters"],"security":[{"cookieAuth":[]}],"parameters":[{"in":"path","name":"letter_id","schema":{"type":"string"},"required":true,"description":"조회할 편지의 고유 ID (nanoid)","example":"e9BAYFSMDiB7X1-Sg0Ozy"}],"responses":{"200":{"description":"편지 조회 성공","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","properties":{"id":{"type":"string","example":"e9BAYFSMDiB7X1-Sg0Ozy"},"senderId":{"type":"string","nullable":true,"example":null},"senderName":{"type":"string","example":"정화"},"receiverName":{"type":"string","example":"지수"},"category":{"type":"string","example":"생일"},"content":{"type":"string","example":"지수야, 생일 축하해. 오늘 하루 즐겁게 보내고 맛있는 것도 많이 먹었으면 좋겠다. 좋은 하루 보내!"},"theme":{"type":"number","example":1},"publishedAt":{"type":"string","format":"date-time","example":"2026-04-28T13:08:29.405Z"}}},"meta":{"type":"object","nullable":true,"example":null},"error":{"type":"object","nullable":true,"example":null}}}}}},"401":{"description":"인증 실패 (토큰 만료 또는 인증 정보 없음)","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"에러 메시지","example":"인증이 만료되었습니다. 다시 시도해주세요."}}}}}},"404":{"description":"해당 편지를 찾을 수 없음","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"data":{"type":"object","nullable":true,"example":null},"meta":{"type":"object","nullable":true,"example":null},"error":{"type":"string","example":"해당 편지를 찾을 수 없습니다."}}}}}},"500":{"description":"서버 오류","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"data":{"type":"object","nullable":true,"example":null},"meta":{"type":"object","nullable":true,"example":null},"error":{"type":"string","example":"서버 내부 오류가 발생했습니다."}}}}}}}}},"/api/letters/{letter_id}/check-password":{"get":{"summary":"편지 비밀번호 유무 확인","description":"편지에 비밀번호가 설정되어 있는지 여부를 반환합니다.","tags":["Letters"],"security":[{"cookieAuth":[]}],"parameters":[{"in":"path","name":"letter_id","required":true,"schema":{"type":"string"},"example":"ffsa8-iEEnfBtKhdKZdIB"}],"responses":{"200":{"description":"비밀번호 설정 여부 조회 성공","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","properties":{"hasPassword":{"type":"boolean","example":true}}},"meta":{"type":"object","nullable":true,"example":null},"error":{"type":"string","example":null}}}}}},"401":{"description":"인증 실패 (토큰 만료 또는 인증 정보 없음)","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"에러 메시지","example":"인증이 만료되었습니다. 다시 시도해주세요."}}}}}},"404":{"description":"해당 편지를 찾을 수 없음","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"data":{"type":"object","nullable":true,"example":null},"meta":{"type":"object","nullable":true,"example":null},"error":{"type":"string","example":"해당 편지를 찾을 수 없습니다."}}}}}}}}},"/api/letters/{letter_id}/verify":{"post":{"operationId":"verifyLetterPassword","summary":"편지 열람 비밀번호 확인","description":"수신자가 입력한 비밀번호가 발신자가 설정한 비밀번호와 일치하는지 확인합니다.","tags":["Letters"],"security":[{"cookieAuth":[]}],"parameters":[{"in":"path","name":"letter_id","schema":{"type":"string"},"required":true,"description":"확인하고자 하는 편지의 고유 ID (nanoid)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["password"],"properties":{"password":{"type":"string","description":"수신자가 입력한 비밀번호 (숫자로 구성된 문자열)","example":"1234"}}}}}},"responses":{"200":{"description":"비밀번호 일치 (확인 성공)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","nullable":true,"example":null},"meta":{"type":"object","nullable":true,"example":null},"error":{"type":"object","nullable":true,"example":null}}}}}},"401":{"description":"인증 실패\n- 비밀번호가 틀린 경우: \"비밀번호가 일치하지 않습니다.\"\n- 비밀번호가 없는 편지에 비번을 입력한 경우: \"이 편지는 비밀번호가 설정되지 않았습니다.\"\n","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"data":{"type":"object","nullable":true,"example":null},"meta":{"type":"object","nullable":true,"example":null},"error":{"type":"string","example":"비밀번호가 일치하지 않습니다."}}}}}},"404":{"description":"존재하지 않는 편지","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"data":{"type":"object","nullable":true,"example":null},"meta":{"type":"object","nullable":true,"example":null},"error":{"type":"string","example":"해당 편지를 찾을 수 없습니다."}}}}}}}}},"/api/users/me/letters/sent":{"get":{"summary":"내가 쓴 편지 목록 조회 (무한 스크롤)","operationId":"getSentLetters","tags":["User Letters"],"parameters":[{"in":"query","name":"cursor","schema":{"type":"string"},"description":"마지막으로 조회된 편지의 ID (Letter.id가 TEXT이므로 string)"}],"responses":{"200":{"description":"조회 성공","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","description":"보관함 레코드 고유 ID","example":1},"senderId":{"type":"integer","nullable":true,"example":123},"senderName":{"type":"string","example":"홍길동"},"receiverName":{"type":"string","example":"김철수"},"category":{"type":"string","example":"일반"},"content":{"type":"string","example":"받은 편지 내용입니다."},"theme":{"type":"integer","example":1},"createdAt":{"type":"string","format":"date-time","description":"편지 발행 시간 (publishedAt)","example":"2026-05-06T12:00:00Z"}}}},"meta":{"type":"object","properties":{"nextCursor":{"type":"integer","nullable":true,"example":10},"hasNextPage":{"type":"boolean","example":true}}},"error":{"type":"object","nullable":true,"example":null}}}}}}}}},"/api/users/me/letters/received":{"get":{"summary":"받은 편지 목록 조회 (무한 스크롤)","operationId":"getReceivedLetters","tags":["User Letters"],"parameters":[{"in":"query","name":"cursor","schema":{"type":"integer"},"description":"마지막으로 조회된 보관함 아이템의 ID (saved_letter.id가 SERIAL이므로 integer)"}],"responses":{"200":{"description":"조회 성공","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","properties":{"letters":{"type":"array","items":{"$ref":"#/components/schemas/SavedLetter"}},"meta":{"type":"object","properties":{"nextCursor":{"type":"integer","nullable":true,"example":15},"hasNextPage":{"type":"boolean","example":false}}}}},"error":{"type":"object","nullable":true,"example":null}}}}}}}},"post":{"summary":"받은 편지 보관하기","operationId":"saveLetter","description":"수신한 편지를 내 계정의 보관함(saved_letter)에 저장합니다.","tags":["User Letters"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"letterId":{"type":"string","example":"letter_nano_id_99"}}}}}},"responses":{"200":{"description":"보관 성공","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"$ref":"#/components/schemas/SavedLetter"},"error":{"type":"object","nullable":true,"example":null}}}}}}}}},"/api/users/me/letters/received/{letterId}":{"delete":{"summary":"보관한 편지 삭제","operationId":"deleteSavedLetter","tags":["User Letters"],"parameters":[{"in":"path","name":"letterId","required":true,"schema":{"type":"string"},"description":"삭제할 편지의 ID (Letter 테이블의 PK)"}],"responses":{"200":{"description":"삭제 성공","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","nullable":true,"example":null},"meta":{"type":"object","nullable":true,"example":null},"error":{"type":"object","nullable":true,"example":null}}}}}}}}},"/api/users/me":{"get":{"summary":"내 정보 조회","description":"글로벌 미들웨어를 통해 발급/확인된 쿠키(JWT)를 기반으로 현재 접속 중인 유저의 데이터베이스 상세 정보를 조회합니다. (GUEST 및 MEMBER 모두 사용 가능)","tags":["Users"],"responses":{"200":{"description":"내 정보 조회 성공","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","properties":{"id":{"type":"integer","example":1},"nanoId":{"type":"string","example":"a1b2c3d4-e5f6-7890"},"userType":{"type":"string","example":"MEMBER"},"kakaoUid":{"type":"string","nullable":true,"example":"1234567890"},"email":{"type":"string","nullable":true,"example":"user@kakao.com"},"nickname":{"type":"string","example":"즐거운팩잇"},"createdAt":{"type":"string","format":"date-time","example":"2026-05-04T04:15:50.000Z"}}},"meta":{"type":"object","nullable":true,"example":null},"error":{"type":"object","nullable":true,"example":null}}}}}},"404":{"description":"데이터베이스에서 유저를 찾을 수 없음","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"data":{"type":"object","nullable":true,"example":null},"meta":{"type":"object","nullable":true,"example":null},"error":{"type":"string","example":"유저 정보를 찾을 수 없습니다."}}}}}},"500":{"description":"서버 내부 오류","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"data":{"type":"object","nullable":true,"example":null},"meta":{"type":"object","nullable":true,"example":null},"error":{"type":"string","example":"서버 내부 오류가 발생했습니다."}}}}}}}}}},"components":{"schemas":{"SavedLetter":{"type":"object","properties":{"id":{"type":"integer","description":"보관함 레코드의 고유 ID (SavedLetter 테이블의 PK)","example":1},"senderId":{"type":"integer","nullable":true,"description":"편지를 보낸 사용자의 ID","example":123},"senderName":{"type":"string","description":"발신인 이름","example":"홍길동"},"receiverName":{"type":"string","description":"수신인 이름","example":"김철수"},"category":{"type":"string","description":"편지 카테고리","example":"응원"},"content":{"type":"string","description":"편지 본문 내용","example":"준비하신 시험 꼭 합격하시길 응원합니다!"},"theme":{"type":"integer","description":"적용된 테마 번호","example":1},"createdAt":{"type":"string","format":"date-time","description":"편지가 발행된 시간 (DB의 published_at 필드)","example":"2026-05-13T18:45:00Z"}}}}},"tags":[{"name":"User Letters","description":"사용자의 편지 관련 API (내가 쓴 편지, 받은 편지 보관 및 조회)"}]}