Repository navigation
API Specification
| 항목 | 내용 |
|---|---|
| 서비스 | AI 뉴스 브리핑 서비스 (백엔드 파트) |
| 범위 | IT 뉴스 6개 분야 (AI, IT 일반, 모바일/통신, 컴퓨터, 보안, 과학) |
| 인증 | JWT를 httpOnly 쿠키(ACCESS_TOKEN)로 전달 / 홈·분야 요약 조회는 인증 불필요 |
| Base URL (로컬) | http://localhost:8080 |
| 데이터 형식 | JSON (Content-Type: application/json) |
변경 (10/14): 분야를 일반 뉴스 6개(정치·경제·사회·생활/문화·IT/과학·스포츠)에서 IT 안의 6개 분야로 바꿨다. 홈 카드 수(6장)는 같다. 후보 기능이던 저장한 기사 API(옛 5.3~5.5)는 제외했다. AI 모델은 Bedrock Nova Lite로 제안됨 (7장, 검증 전).
변경 (10/15): FE 제안으로 분야에서 게임(
GAME)을 빼고 AI(AI)를 추가했다 (AI가 홈 첫 카드). 분야 이슈에keyPoints·timeline·glossary, 홈 이슈에 **summary**를 추가했다.summary는 한 줄 요약이다. 마이페이지 저장한 기사(5.3~5.6)를 다시 추가했다.변경 (10/15 회의): 분야에서 인터넷(
INTERNET)을 빼고 보안(SECURITY)을 추가했다. 순서는 AI → IT 일반 → 모바일/통신 → 컴퓨터 → 보안 → 과학. 플랫폼·유통 피드(60)는 더 이상 수집하지 않는다.변경 (10/19, M3-7): 실제 응답과 대조해 예시를 맞췄다 (홈·분야·질문·마이페이지·저장 기사·인증, 필드 차이 0건). 401·400 에러 예시 추가. 외부 검색 API(네이버)를 쓰지 않기로 해 관련 설명을 지웠다.
/actuator/prometheus는 M4 예정으로 표시.
- 로그인·회원가입 성공 시 서버가 쿠키를 내려준다. FE는 토큰을 직접 다루지 않는다.
Set-Cookie: ACCESS_TOKEN=<JWT>; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=86400 - FE는 모든 API 요청에 쿠키가 실리도록
fetch(url, { credentials: 'include' })로 호출한다. -
FE와 API는 같은 사이트(같은 상위 도메인)에서 서비스한다. 다른 사이트면 브라우저가 쿠키를 막을 수 있다.
- 온프레미스: Nginx가
/api/*→ 백엔드로 프록시 (같은 출처, CORS 불필요) - AWS: 화면
app.{도메인}(CloudFront), APIapi.{도메인}(ALB). Route 53 도메인 사용 - AWS와 로컬 개발(FE dev 서버 →
localhost:8080)에서는 CORS로 화면 주소만 허용하고Access-Control-Allow-Credentials: true를 준다.
- 온프레미스: Nginx가
- 로컬(http)에서는
Secure속성을 끈다 (환경 변수COOKIE_SECURE=false). - CSRF:
SameSite=Lax+ JSON 요청만 허용으로 막는다. - 토큰 만료(24시간) 후에는
401 UNAUTHORIZED→ FE는 로그인 팝업을 띄운다.
- 실패 응답은 모두 아래 형태를 따른다.
error는 화면에 그대로 보여줄 문구,code는 FE가 분기할 때 쓰는 값이다 (전체 목록은 6장).{ "error": "사용자에게 보여줄 메시지", "code": "ERROR_CODE" } -
401 예시 — 쿠키가 없거나 토큰이 만료된 상태로 로그인이 필요한 API를 호출 (FE는 로그인 팝업)
로그인 실패는 같은 401이지만 code가 다르다:
{ "error": "로그인이 필요합니다.", "code": "UNAUTHORIZED" }{ "error": "이메일 또는 비밀번호가 올바르지 않습니다.", "code": "INVALID_CREDENTIALS" } -
400 예시 — 입력값 검증 실패. 여러 항목이 틀려도 첫 번째 오류 하나만
error에 담는다JSON 형식이 깨졌거나 숫자 자리에 글자가 온 경우(예:{ "error": "질문을 입력해 주세요.", "code": "VALIDATION_ERROR" }/api/questions/abc):{ "error": "요청 형식이 올바르지 않습니다.", "code": "VALIDATION_ERROR" } - 날짜·시간은 ISO 8601 + 오프셋 (
2026-10-07T11:30:00+09:00). "10시간 전" 같은 상대 표기는 FE가 만든다. - 분야(category)는 아래 코드로 주고받는다. 한글 이름은 응답의
categoryName에만 넣는다. 홈 카드는 이 표의 순서대로 온다.
| code | 이름 |
|---|---|
AI |
AI |
IT_GENERAL |
IT 일반 |
MOBILE_TELECOM |
모바일/통신 |
COMPUTER |
컴퓨터 |
SECURITY |
보안 |
SCIENCE |
과학 |
| 메서드 | 경로 | 용도 |
|---|---|---|
| GET | /actuator/health/liveness |
Liveness Probe. 앱 프로세스 생존 여부 |
| GET | /actuator/health/readiness |
Readiness Probe. DB 연결 포함 준비 상태 |
| GET | /actuator/info |
배포된 커밋 SHA·빌드 시각 |
| GET | /actuator/prometheus |
Prometheus 메트릭. M4에서 추가 예정 (현재 404) |
liveness·readiness 응답 (200 OK) / 준비 안 됨이면 503 + "status": "DOWN"
{ "status": "UP" }info 응답 (200 OK) — commit은 CI가 이미지에 넣은 커밋 SHA (로컬 빌드는 unknown)
{
"commit": "dc27a3fad5d5ade7c4bd4439717322d9f0bd60b7",
"build": { "artifact": "likelion-devops-7th-team01-backend", "name": "likelion-devops-7th-team01-backend",
"time": "2026-10-19T00:08:28.483Z", "version": "0.0.1-SNAPSHOT", "group": "com.likelion" }
}Redis는 readiness에 넣지 않는다. Redis가 죽어도 DB로 서비스가 계속되어야 하므로(M6 장애 시나리오), Redis 장애로 Pod가 트래픽에서 빠지면 안 된다.
가입 즉시 로그인 상태가 된다 (쿠키 발급). 로그인 팝업에서 가입 후 하려던 동작을 바로 이어가기 위함.
요청
| 파라미터 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
email |
body | String | Y | 이메일 형식, 최대 255자 |
password |
body | String | Y | 8~64자 |
nickname |
body | String | Y | 1~20자 |
응답 (201 Created) + Set-Cookie: ACCESS_TOKEN=...
{
"userId": 1,
"email": "user@example.com",
"nickname": "용민"
}| 상태 | code | 상황 |
|---|---|---|
| 400 | VALIDATION_ERROR |
형식·길이 오류 |
| 409 | DUPLICATE_EMAIL |
이미 가입된 이메일 |
요청
| 파라미터 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
email |
body | String | Y | 이메일 |
password |
body | String | Y | 비밀번호 |
응답 (200 OK) + Set-Cookie: ACCESS_TOKEN=...
{
"userId": 1,
"email": "user@example.com",
"nickname": "용민"
}| 상태 | code | 상황 |
|---|---|---|
| 401 | INVALID_CREDENTIALS |
이메일 또는 비밀번호 불일치 |
쿠키를 삭제한다. 로그인 상태가 아니어도 항상 성공한다.
응답 (204 No Content) + Set-Cookie: ACCESS_TOKEN=; Max-Age=0; Path=/
httpOnly 쿠키는 JS에서 읽을 수 없으므로, FE는 앱 시작 시 이 API로 로그인 여부를 판단해 헤더 버튼(로그인/로그아웃)을 그린다.
응답 (200 OK)
{
"userId": 1,
"email": "user@example.com",
"nickname": "용민"
}응답 (401) UNAUTHORIZED → 비로그인 상태로 처리
요약은 서버가 30분마다 미리 만들어 둔다. 조회 API는 저장된 결과만 돌려주므로 AI를 호출하지 않는다. 갱신에 실패하면 직전 요약을 그대로 돌려준다.
- 응답은 Redis에 캐시한다 (캐시 우선, 없으면 DB, TTL 30분). 요약 배치가 끝나면 캐시를 비우므로 새 요약은 바로 보인다.
- Redis가 죽어도 DB에서 읽어 정상 응답한다 (응답 시간만 늘어남).
6개 분야 카드. 분야마다 이슈 제목 3개. 분야 순서는 위 분야 코드표 순서.
응답 (200 OK)
{
"generatedAt": "2026-10-14T11:30:00+09:00",
"mock": false,
"categories": [
{
"category": "AI",
"categoryName": "AI",
"issues": [
{
"issueId": 501,
"title": "AI 기본법 시행령 확정…고영향 AI 사업자 의무 구체화",
"summary": "채용·의료·금융 등 고영향 분야 AI는 위험 관리와 설명 의무를 지게 됐어요."
},
{ "issueId": 502, "title": "...", "summary": "..." },
{ "issueId": 503, "title": "...", "summary": "..." }
]
}
]
}-
mock:true이면 FE는 화면 위에 "샘플 데이터" 띠를 띄운다. -
summary: 한 줄 요약. FE는 분야마다 1번 이슈에만 보여 준다. - 아직 한 번도 요약이 만들어지지 않은 분야는
issues: [](FE는 빈 결과 상태 표시). - 갱신 실패 여부는 별도 필드 없이
generatedAt으로 판단한다. FE는generatedAt이 1시간 넘게 지났으면 "뉴스 갱신이 지연되고 있어요" 안내를 띄운다. (3.2도 같음)
요청
| 파라미터 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
category |
path | String | Y | 분야 코드 (AI 등) |
응답 (200 OK)
{
"generatedAt": "2026-10-14T11:30:00+09:00",
"mock": false,
"category": "AI",
"categoryName": "AI",
"issues": [
{
"issueId": 501,
"title": "AI 기본법 시행령 확정…고영향 AI 사업자 의무 구체화",
"summary": "채용·의료·금융 등 고영향 분야 AI는 위험 관리와 설명 의무를 지게 됐어요.",
"keyPoints": [
"고영향 AI의 범위를 채용, 대출 심사, 의료 진단 등으로 명시",
"생성형 AI 결과물에는 AI가 만들었다는 표시 의무화",
"계도 기간 1년을 두고 과태료 부과는 유예"
],
"timeline": [
{ "date": "2026-10-13", "text": "AI 기본법 시행령 국무회의 의결" },
{ "date": "2026-10-14", "text": "AI 기본법 시행령 확정, 고영향 AI 기준 나왔다" }
],
"glossary": [
{ "term": "고영향 AI", "description": "사람의 생명·안전·기본권에 큰 영향을 줄 수 있는 분야에 쓰이는 AI예요." }
],
"sources": [
{
"press": "전자신문",
"title": "AI 기본법 시행령 확정, 고영향 AI 기준 나왔다",
"url": "https://www.etnews.com/...",
"publishedAt": "2026-10-14T01:20:00+09:00"
}
]
}
]
}- 분야당 이슈는 최대 5개 (
rank_no순). 홈은 그중 1~3번. -
summary: 한 줄 요약.keyPoints: 핵심 포인트 (3개 안팎). -
timeline: 전개 흐름. 근거 기사(sources)의 발행일(date, YYYY-MM-DD, KST)과 제목(text)으로 만든다 (오래된 순). AI가 기사에 없는 날짜·사건을 지어내지 않게 AI에게 맡기지 않는다. 발행일 없는 기사는 빠지므로 빈 배열일 수 있다 → FE는 빈 배열이면 영역을 숨긴다. -
glossary: 용어 풀이. 풀 용어가 없으면 빈 배열일 수 있다. - mock일 때:
summary는[샘플]+ 기사 소개글 첫 문장,keyPoints는 이어지는 문장(최대 3개),glossary는 고정 예시 1개. - 홈에서 제목을 눌러 들어온 경우 FE는
issueId가 일치하는 이슈를 펼친다 (예:/category/AI?open=501).
| 상태 | code | 상황 |
|---|---|---|
| 404 | NOT_FOUND |
없는 분야 코드 (옛 코드 POLITICS, GAME, INTERNET 등 포함) |
-
POST로 질문을 보내면 DB에PENDING으로 즉시 기록하고, Redis 대기열에 질문 ID를 넣은 뒤202를 반환한다. - 워커(별도 Deployment, 대기열 길이에 따라 KEDA로 확장)가 대기열에서 질문을 꺼내
PENDING→PROCESSING으로 바꾸고(이미 다른 워커가 바꿨으면 건너뜀), 관련 기사를 찾아 AI로 답을 만든다.- Redis 장애 전환으로 대기열 항목이 사라질 수 있으므로, 30초 넘게
PENDING인 질문은 서버가 대기열에 다시 넣는다. 질문의 실제 상태는 항상 DB가 기준이다.
- Redis 장애 전환으로 대기열 항목이 사라질 수 있으므로, 30초 넘게
- 결과에 따라
COMPLETED/REJECTED(뉴스와 무관한 질문) /FAILED로 바뀐다. - FE는
GET /api/questions/{id}를 2초 간격으로 폴링한다. 처리 중 다른 화면으로 가도 마이페이지에서 이어서 볼 수 있다. - 60초 넘게 끝나지 않은 질문은 서버가
FAILED로 바꾼다.
| status | 의미 | FE 표시 |
|---|---|---|
PENDING |
접수됨, 대기 중 | 처리 중 |
PROCESSING |
AI 처리 중 | 처리 중 |
COMPLETED |
답 생성 완료 | 답 + 근거 기사 |
REJECTED |
뉴스와 무관한 질문 | 거절 안내 (answer에 안내 문구) |
FAILED |
처리 실패 (AI 오류·타임아웃) | 실패 안내 |
요청
| 파라미터 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
query |
body | String | Y | 질문 내용, 1~500자 |
응답 (202 Accepted)
{
"questionId": 123,
"status": "PENDING"
}| 상태 | code | 상황 |
|---|---|---|
| 400 | VALIDATION_ERROR |
빈 질문, 500자 초과 |
| 429 | RATE_LIMIT_EXCEEDED |
하루 질문 10회 초과 (자정 KST 기준 초기화, 거절(REJECTED)된 질문은 포함, 실패(FAILED)한 질문은 제외) |
응답 (200 OK)
{
"questionId": 123,
"status": "COMPLETED",
"query": "요즘 AI 반도체 시장 어때?",
"answer": "최근 AI 반도체 시장은 데이터센터 수요로 인해...",
"sources": [
{
"press": "전자신문",
"title": "AI 반도체 투자 확대...",
"url": "https://www.etnews.com/...",
"publishedAt": "2026-10-14T09:00:00+09:00"
}
],
"saved": false,
"createdAt": "2026-10-14T11:00:00+09:00",
"completedAt": "2026-10-14T11:00:08+09:00"
}-
PENDING·PROCESSING일 때answer,sources,completedAt은null. -
saved: 질문 저장(별) 여부.
| 상태 | code | 상황 |
|---|---|---|
| 404 | NOT_FOUND |
없는 질문 또는 다른 사용자의 질문 (존재 여부를 숨기기 위해 403 대신 404) |
질문을 삭제한다. 저장한 질문에서도 함께 사라진다.
응답 (204 No Content)
| 상태 | code | 상황 |
|---|---|---|
| 404 | NOT_FOUND |
없는 질문 또는 다른 사용자의 질문 |
여러 번 호출해도 결과가 같다 (이미 저장된 질문이면 그대로 성공).
응답 (204 No Content)
응답 (204 No Content)
4.4·4.5 공통 오류: 404 NOT_FOUND (없는 질문 또는 다른 사용자의 질문)
마이페이지는 질문 기록, 저장한 질문, 저장한 기사 세 구역이다. 목록은 최신순. 페이지 크기는 고정(size=20), page는 0부터.
응답 (200 OK)
{
"questions": [
{
"questionId": 123,
"query": "요즘 AI 반도체 시장 어때?",
"status": "COMPLETED",
"saved": true,
"createdAt": "2026-10-14T11:00:00+09:00"
}
],
"hasNext": false
}응답 형태는 5.1과 같고, saved: true인 질문만 저장한 순서(최신순)로 준다. 상세는 GET /api/questions/{id}로 본다.
분야 페이지의 근거 기사(sources 항목) 하나를 저장한다. 요청 본문은 sources 항목을 그대로 보낸다. 여러 번 호출해도 결과가 같다 (이미 저장한 기사면 그대로 성공). 원본 기사는 7일 뒤 지워지므로 저장할 때 내용을 복사해 둔다.
요청
| 파라미터 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
title |
body | String | Y | 기사 제목, 최대 255자 |
url |
body | String | Y | 기사 주소, http:// 또는 https://로 시작, 최대 512자 |
press |
body | String | N | 언론사, 최대 100자 |
publishedAt |
body | String | N | 발행 시각 (ISO 8601 + 오프셋) |
{
"press": "전자신문",
"title": "AI 기본법 시행령 확정, 고영향 AI 기준 나왔다",
"url": "https://www.etnews.com/20261014000001",
"publishedAt": "2026-10-14T09:00:00+09:00"
}응답 (204 No Content)
| 상태 | code | 상황 |
|---|---|---|
| 400 | VALIDATION_ERROR |
제목·주소 없음, 길이 초과, http(s)가 아닌 주소 |
url은 URL 인코딩해서 보낸다 (encodeURIComponent).
응답 (204 No Content)
| 상태 | code | 상황 |
|---|---|---|
| 404 | NOT_FOUND |
저장하지 않은 기사 |
분야 페이지에서 근거 기사마다 저장 버튼을 채워 그릴지 판단할 때 쓴다. 분야 응답은 모든 사용자가 같이 쓰는 캐시라 사용자별 저장 여부를 넣지 않는다.
응답 (200 OK)
{ "urls": ["https://www.etnews.com/20261014000001"] }최근에 저장한 순.
응답 (200 OK)
{
"articles": [
{
"savedArticleId": 7,
"press": "전자신문",
"title": "AI 기본법 시행령 확정, 고영향 AI 기준 나왔다",
"url": "https://www.etnews.com/20261014000001",
"publishedAt": "2026-10-14T09:00:00+09:00",
"savedAt": "2026-10-15T14:00:00+09:00"
}
],
"hasNext": false
}| 상태 | code | 상황 |
|---|---|---|
| 400 | VALIDATION_ERROR |
요청 값 형식·길이 오류 |
| 401 | INVALID_CREDENTIALS |
로그인 시 이메일 또는 비밀번호 불일치 |
| 401 | UNAUTHORIZED |
쿠키 없음, 토큰이 유효하지 않거나 만료 |
| 404 | NOT_FOUND |
없는 리소스 또는 다른 사용자의 리소스 |
| 409 | DUPLICATE_EMAIL |
이미 가입된 이메일 |
| 429 | RATE_LIMIT_EXCEEDED |
하루 질문 10회 초과 |
| 500 | INTERNAL_SERVER_ERROR |
서버 내부 오류 |
AI·뉴스 API 실패는 사용자 요청에 500으로 드러나지 않는다. 요약 갱신 실패 → 직전 요약 유지, 질문 처리 실패 → 질문 상태가
FAILED.
-
IT 6분야 뉴스 수집: 전자신문 분야별 RSS를 30분마다 수집한다. 제목·요약문·링크·언론사·날짜만 저장하고 7일 보관. 요약에는 최근 48시간 기사를 쓴다.
분야 RSS (rss.etnews.com) AI 04046.xml(AI·SW > AI)모바일/통신 03033.xml(통신)컴퓨터 04043.xml(SW)보안 04045.xml(AI·SW > 보안)과학 20.xml(과학)IT 일반 03.xml,04.xml,06.xml(IT·SW·전자 상위 피드)같은 기사(URL)는 한 분야에만 저장된다. IT 일반은 상위 피드라 다른 분야 기사가 섞여 있으므로 맨 마지막에 수집해서, 세부 분야에 들어가지 않은 기사만 IT 일반이 갖는다.
-
질문 방식 (10/15 회의): 자유 입력으로 받되 뉴스(기사)에 관한 질문만 답한다. 질문으로 기사를 찾아 그 기사만 근거로 답하고, 뉴스와 무관한 질문(코드 작성·번역·잡담 등)은
REJECTED로 거절한다. 이슈를 골라 질문하는 방식은 하지 않는다. -
질문 근거 (10/16 결정): 질문 답변은 서비스가 수집한 전자신문 IT 기사(최근 7일) 범위 안에서만 한다. 외부 검색 API(네이버 등)는 쓰지 않는다 — 네이버 검색 API가 NAVER API Hub로 옮겨지며 결제수단 등록이 필수가 됐다. 관련 기사가 없으면 AI를 부르지 않고 "관련 기사를 찾지 못했다"고 안내.
-
AI 서비스 (검증 전 제안, 10/14 CL): Amazon Bedrock Nova Lite. 서울 리전(
ap-northeast-2)에서 교차 리전 추론으로 호출한다. 한국어 요약·출처 정확성·응답 형식을 확인한 뒤 확정하고, 품질이 부족하면 Nova Pro와 비교한다. Bedrock을 쓸 수 없으면 AI 회사 API 직접 호출.- 요약과 질문 답변 모두 같은 모델을 쓴다. 전환은
AI_MODE=mock | bedrock(docs/env.md). - 실제 AI는 점검일에만 켠다: 4주차 점검·6주차 리허설·발표, 각 최대 4시간 (총 12시간). 개발·부하 테스트는 mock.
- 예상 사용량: 질문 150건 포함 입력 약 160만·출력 약 29만 토큰 (추가 테스트·재시도 별도).
- 요약과 질문 답변 모두 같은 모델을 쓴다. 전환은
-
저장한 기사: 10/14에 뺐다가 10/15 FE 화면(마이페이지 탭)에 맞춰 다시 넣었다. 분야 페이지의 근거 기사 하나에 저장 버튼이 붙는다 (5.3~5.6). 테이블은 V2에서 지우고 V4에서 다시 만들었다.
- 팀 AWS 계정의 Nova Lite 호출 권한 확인 → 한국어 요약 품질 검증 후 모델 확정 (4주차 점검 전)
- FE와 합의: 새 분야 코드, 쿠키·같은 출처 구성(Nginx
/api프록시, CloudFront/api/*behavior), 폴링 2초·타임아웃 60초