Skip to content

Latest commit

ย 

History

History
435 lines (347 loc) ยท 8.75 KB

api-spec.md

File metadata and controls

435 lines (347 loc) ยท 8.75 KB

๊ณตํ†ต ์‚ฌํ•ญ

Base URL

https://{host}/api/v1

๊ณตํ†ต ์‘๋‹ต ํฌ๋งท

// ์„ฑ๊ณต
{
  "success": true,
  "data": { }
}

// ์‹คํŒจ
{
  "success": false,
  "error": {
    "code": "INVALID_CREDENTIALS",
    "message": "์•„์ด๋”” ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋‹ค์‹œ ํ™•์ธํ•ด์ฃผ์„ธ์š”."
  }
}

์ธ์ฆ ๋ฐฉ์‹ โ€” JWT (Access + Refresh, ํšŒ์ „ ๋ฐฉ์‹)

๋กœ๊ทธ์ธ ์„ฑ๊ณต ์‹œ Access Token, Refresh Token์„ ํ•จ๊ป˜ ๋ฐœ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค.

ํ† ํฐ ์ €์žฅ ์œ„์น˜(๊ถŒ์žฅ) ๋งŒ๋ฃŒ ๋น„๊ณ 
Access Token ํด๋ผ์ด์–ธํŠธ ๋ฉ”๋ชจ๋ฆฌ / SecureStore 15~30๋ถ„ ๋งค ์š”์ฒญ ํ—ค๋”์— ํฌํ•จ (ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ ์ œ์™ธ)
Refresh Token HttpOnly Secure Cookie (์›น) / SecureStore (์•ฑ) 14~30์ผ ์žฌ๋ฐœ๊ธ‰ ์‹œ ํšŒ์ „(rotate)
Authorization: Bearer {access_token}

Refresh Token ํšŒ์ „(Rotation) ๋ฐฉ์‹

  • Access Token ๋งŒ๋ฃŒ ์‹œ /auth/refresh ํ˜ธ์ถœ โ†’ ์ƒˆ Access Token + ์ƒˆ Refresh Token ๋ฐœ๊ธ‰, ๊ธฐ์กด Refresh Token์€ ์ฆ‰์‹œ ํ๊ธฐ
  • ํƒˆ์ทจ๋œ ํ† ํฐ ์žฌ์‚ฌ์šฉ ํƒ์ง€: ์ด๋ฏธ ํ๊ธฐ๋œ Refresh Token์œผ๋กœ ์žฌ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ ์ „์ฒด ์„ธ์…˜์„ ๊ฐ•์ œ ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ
  • ์„œ๋ฒ„๋Š” Refresh Token์„ ํ•ด์‹œ๋กœ ์ €์žฅ(์›๋ฌธ ์ €์žฅ ๊ธˆ์ง€)

์—๋Ÿฌ ์ฝ”๋“œ

์ฝ”๋“œ ๋ฐœ์ƒ ํ™”๋ฉด ๋ฉ”์‹œ์ง€
INVALID_CREDENTIALS ๋กœ๊ทธ์ธ ์•„์ด๋”” ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋‹ค์‹œ ํ™•์ธํ•ด์ฃผ์„ธ์š”.
DUPLICATE_ID ํšŒ์›๊ฐ€์ž… ์ด๋ฏธ ์‚ฌ์šฉ ์ค‘์ธ ์•„์ด๋””์˜ˆ์š”. ๋‹ค์‹œ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.
TOKEN_EXPIRED ์ธ์ฆ ํ•„์š” API ์ „๋ฐ˜ ๋กœ๊ทธ์ธ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•ด์ฃผ์„ธ์š”.
REFRESH_TOKEN_REUSED ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ๋น„์ •์ƒ์ ์ธ ์ ‘๊ทผ์ด ๊ฐ์ง€๋˜์–ด ๋กœ๊ทธ์•„์›ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์นดํ…Œ๊ณ ๋ฆฌ

topics: ์ผ / ๊ด€๊ณ„ / ๋ˆ / ๊ฑด๊ฐ• / ๋‚˜ (+๊ธฐํƒ€)

values: ์„ฑ์žฅ / ์•ˆ์ • / ์ž์œจ / ์—ฐ๊ฒฐ / ์ธ์ • / ์žฌ๋ฏธ / ํšจ์œจ / ์˜๋ฏธ / ์ฑ…์ž„ (+๊ธฐํƒ€)

API ๋ฌธ์„œ

์ธ์ฆ(Auth)

์•„์ด๋”” ์ค‘๋ณต ํ™•์ธ

  • GET /auth/id/check?id={id}

Response

{ 
	"success": true,
	"data": { 
		"available": true|false
	}
}

ํšŒ์›๊ฐ€์ž…

  • POST /auth/signup

Request

{
  "id": "layer2026",
  "nickname": "string",
  "password": "string",
  "passwordConfirm": "string"
}

Response

{
  "success": true,
  "data": {
    "userId": "uuid"
  }
}

๋กœ๊ทธ์ธ

  • POST /auth/login

Request

{
	"id": "layer2026",
	"password": "string"
}

Response

{
  "success": true,
  "data": {
    "accessToken": "string",
    "refreshToken": "string",
    "user": {
      "id": "uuid",
      "loginId": "layer2026",
      "nickname": "string"
    },
    "activateOnboarding": true|false // ์ฒซ ๋กœ๊ทธ์ธ ์‹œ true
  }
}

๋กœ๊ทธ์•„์›ƒ

  • POST /auth/logout
  • ์ „๋‹ฌ๋ฐ›์€ Refresh Token์„ ์ฆ‰์‹œ ํ๊ธฐํ•œ๋‹ค.

Request

{ "refreshToken": "string" }

Response

{
  "success": true,
  "data": null
}

ํšŒ์› ํƒˆํ‡ด

  • DELETE /auth/delete
  • ๊ณ„์ • ์ •๋ณด, ๊ณ ๋ฏผ, ๊ธฐ๋ก, ์ธ์‚ฌ์ดํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ์‚ญ์ œํ•˜๊ณ  ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ ์ „์ฒด ์„ธ์…˜(Refresh Token)์„ ํ๊ธฐํ•œ๋‹ค.

Response

{
  "success": true,
  "data": null
}

ํ† ํฐ ์žฌ๋ฐœ๊ธ‰

  • POST /auth/refresh

Request

{ "refreshToken": "string" }

Response

{
  "success": true,
  "data": {
    "accessToken": "string",
    "refreshToken": "string"
  }
}

ํ™ˆ(Home)

ํ™ˆ ํ™”๋ฉด ์กฐํšŒ

  • GET /home
  • ongoingConcerns: ์ง„ํ–‰ ์ค‘(PENDING) ์ƒํƒœ์˜ ๊ณ ๋ฏผ์„ ๋งˆ์ง€๋ง‰ ๊ธฐ๋ก์ผ ์˜ค๋ž˜๋œ ์ˆœ์œผ๋กœ ์ •๋ ฌํ•˜์—ฌ ์ตœ๋Œ€ 3๊ฑด ๋ฐ˜ํ™˜ํ•œ๋‹ค.
  • ongoingConcernCount: ์ง„ํ–‰ ์ค‘์ธ ๊ณ ๋ฏผ์˜ ์ „์ฒด ๊ฐœ์ˆ˜ (๋ฐฐ์—ด ๊ธธ์ด์™€ ๋ณ„๊ฐœ)
  • recentRecords: ์ž‘์„ฑ์ผ ์ตœ์‹ ์ˆœ์œผ๋กœ ์ตœ๋Œ€ 3๊ฑด ๋ฐ˜ํ™˜ํ•œ๋‹ค.

Response

{
  "success": true,
  "data": {
    "nickname": "๋‹‰๋„ค์ž„",
    "ongoingConcernCount": 5,
    "ongoingConcerns": [
      {
        "concernId": "uuid",
        "concern": "A์‚ฌ vs B์‚ฌ",
        "topic": "์ผ",
		"topicOther": "", // topic์ด ๊ธฐํƒ€์ผ ๊ฒฝ์šฐ ์‚ฌ์šฉ
        "lastRecordDate": "2026-07-24"
      }
    ],
    "monthlyValueHighlight": {
      "topValue": "์„ฑ์žฅ",
      "changeRateVsLastMonth": 12
    },
    "recentRecords": [
      {
        "recordId": "uuid",
        "decision": "A๋กœ ๋งˆ์Œ์ด ๊ธฐ์›€",
        "date": "2026-07-27",
        "value": "์„ฑ์žฅ"
      }
    ]
  }
}

๊ณ ๋ฏผ(Concerns)

์ƒˆ ๊ณ ๋ฏผ ์ž‘์„ฑ

  • POST /concerns
  • topicOther: topic์ด ๊ธฐํƒ€์ผ ๋•Œ๋งŒ ์‚ฌ์šฉํ•˜๋ฉฐ ์ด ๊ฒฝ์šฐ ํ•„์ˆ˜๋‹ค. ๋‹ค๋ฅธ ์ฃผ์ œ์—์„œ๋Š” ๊ฐ’์„ ๋ณด๋‚ด๋„ ๋ฌด์‹œํ•œ๋‹ค(null ์ €์žฅ).

Request

{
    "concern": "A์‚ฌ vs B์‚ฌ",
	"topic": "์ผ",
	"topicOther": "", // topic์ด ๊ธฐํƒ€์ผ ๊ฒฝ์šฐ ์‚ฌ์šฉ
    "decision": "์•„์ง ๋ชป ์ •ํ•จ / A๋กœ ๋งˆ์Œ์ด ๊ธฐ์›€",
    "reason": "ํ•œ ์ค„์ด๋ฉด ์ถฉ๋ถ„ํ•ด์š”",
    "value": "์„ฑ์žฅ",
    "concernStatus": "PENDING|RESOLVED"
}

์ด์–ด์“ธ ๊ณ ๋ฏผ ์กฐํšŒ

  • GET /concerns/pending
  • ์ง„ํ–‰ ์ค‘(PENDING) ์ƒํƒœ์˜ ๊ณ ๋ฏผ๋งŒ ์กฐํšŒํ•œ๋‹ค. ์ •๋ฆฌ๋œ(RESOLVED) ๊ณ ๋ฏผ์€ ํฌํ•จํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • ๋งˆ์ง€๋ง‰ ๊ธฐ๋ก์ผ ์ตœ์‹ ์ˆœ์œผ๋กœ ์ •๋ ฌํ•œ๋‹ค.

Response

{
	"success": true,
	"data": {
		"ongoingConcerns": [
			{
				"concernId": "uuid",
				"concern": "A์‚ฌ vs B์‚ฌ",
				"topic": "์ผ",
				"topicOther": "", // topic์ด ๊ธฐํƒ€์ผ ๊ฒฝ์šฐ ์‚ฌ์šฉ
				"lastRecordDate": "2026-07-24",
				"recordCount": 4
			}
		]
	}
}

์ด์–ด์“ธ ์ง€๋‚œ ๊ธฐ๋ก ์กฐํšŒ

  • GET /concerns/pending/{concernId}
  • ํ•ด๋‹น ๊ณ ๋ฏผ์˜ ์ง€๋‚œ ๊ธฐ๋ก์„ ์˜ค๋ž˜๋œ ์ˆœ์œผ๋กœ ์ •๋ ฌํ•œ๋‹ค.

Response

{
	"success": true,
	"data": {
		"concern": "A์‚ฌ vs B์‚ฌ",
		"records": [
			{
				"recordId": "uuid",
				"decision": "A์‚ฌ๊ฐ€ ์กฐ๊ธˆ ๋” ๋งˆ์Œ์— ๋‚จ",
				"value": "์„ฑ์žฅ",
				"createdAt": "2026-07-20"
			}
		]
	}
}

์ด์–ด์“ฐ๊ธฐ ๊ธฐ๋ก ์ž‘์„ฑ

  • POST /concerns/pending/{concernId}

Request

{
	"decision": "์•„์ง ๋ชป ์ •ํ•จ / A๋กœ ๋งˆ์Œ์ด ๊ธฐ์›€",
	"reason": "ํ•œ ์ค„์ด๋ฉด ์ถฉ๋ถ„ํ•ด์š”",
	"value": "์„ฑ์žฅ",
	"concernStatus": "PENDING|RESOLVED"
}

์ธ์‚ฌ์ดํŠธ(Insights)

์ธ์‚ฌ์ดํŠธ ๋ฉ”์ธ ์กฐํšŒ

  • GET /insights?startDate=2025-07-01&endDate=2026-08-01&topics=์ผ&values=์„ฑ์žฅ&status=PENDING
  • valueByTopic: ๊ธฐ๋ก ์ˆ˜๊ฐ€ ๋งŽ์€ ์ˆœ์œผ๋กœ ์ •๋ ฌํ•œ๋‹ค.
  • trend: ์กฐํšŒ ๊ธฐ๊ฐ„์„ ํ•ญ์ƒ 4๊ฐœ ๊ตฌ๊ฐ„์œผ๋กœ ๊ท ๋“ฑ ๋ถ„ํ• ํ•˜๋ฏ€๋กœ ๋ฐฐ์—ด ๊ธธ์ด๋Š” 4๋กœ ๊ณ ์ •๋œ๋‹ค. ๊ตฌ๊ฐ„๋ณ„ startDate/endDate๋Š” ์„œ๋ฒ„๊ฐ€ ๊ณ„์‚ฐํ•œ๋‹ค.
  • trend์˜ valueDistribution์€ ์กฐํšŒ ๊ธฐ๊ฐ„ ์ „์ฒด์— ๋“ฑ์žฅํ•œ ๋ชจ๋“  ๊ฐ€์น˜๋ฅผ ๋งค ๊ตฌ๊ฐ„์— ํฌํ•จํ•˜๋ฉฐ, ํ•ด๋‹น ๊ตฌ๊ฐ„์— ๊ธฐ๋ก์ด ์—†๋Š” ๊ฐ€์น˜๋Š” percentage๊ฐ€ 0์ด๋‹ค.
  • largestIncrease / largestDecrease: ์ฆ๊ฐํญ์ด ๊ฐ€์žฅ ํฐ ๊ฐ€์น˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ๋™๋ฅ ์ธ ๊ฒฝ์šฐ์—๋งŒ ๋ณต์ˆ˜ ํ•ญ๋ชฉ์„ ํฌํ•จํ•œ๋‹ค.
  • ๊ธฐํƒ€ ์ฃผ์ œ๋Š” topicOther ๊ฐ’๊ณผ ๋ฌด๊ด€ํ•˜๊ฒŒ ํ•˜๋‚˜๋กœ ๋ฌถ์–ด ์ง‘๊ณ„ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ์ง‘๊ณ„ ์‘๋‹ต(valueByTopic, ๊ธฐ๋ก ๋ชฉ๋ก ์กฐํšŒ)์˜ topicOther๋Š” ํ•ญ์ƒ null์ด๋‹ค.

Response

{
  "success": true,
  "data": {
	  "valueByTopic" : [
			{
			  "topic": "์ผ",
			  "topicOther": "", // topic์ด ๊ธฐํƒ€์ผ ๊ฒฝ์šฐ ์‚ฌ์šฉ
			  "valueDistribution": [
				  { "value": "์„ฑ์žฅ", "percentage": 62 }
		        ],
			  "count": 12
            }
	  ],
	  "trend": [
		  {
			  "startDate": "2026-06-30",
			  "endDate": "2026-07-06",
			  "valueDistribution": [
					{ "value": "์„ฑ์žฅ", "percentage": 47 }
				]
			}
	  ],
	  "largestIncrease": [
			{ "value": "์„ฑ์žฅ", "increaseRate": 16 }
		],
		"largestDecrease": [
			{ "value": "์•ˆ์ •", "decreaseRate": -4 }
		],
	  "insight": {
		  "mostTopic": ["์ผ"],
		  "mostValue": ["์„ฑ์žฅ"]
	  },
	  "totalCount": 48
	}
}

๊ธฐ๋ก ๋ชฉ๋ก ์กฐํšŒ

  • GET /insights/{topic}/records?startDate=2025-07-01&endDate=2026-08-01
  • {topic}์€ ์ฃผ์ œ๋ช… ๋ฌธ์ž์—ด์ด๋ฉฐ URL ์ธ์ฝ”๋”ฉํ•˜์—ฌ ์ „๋‹ฌํ•œ๋‹ค.
  • ๊ธฐ๋ก์€ ์ตœ์‹ ์ˆœ์œผ๋กœ ์ •๋ ฌํ•œ๋‹ค.

Response

{
	"success": true,
	"data": {
		"topic": "๊ฑด๊ฐ•",
		"topicOther": "", // topic์ด ๊ธฐํƒ€์ผ ๊ฒฝ์šฐ ์‚ฌ์šฉ
		"records": [
			{
				"recordId": "uuid",
				"decision": "ํ—ฌ์Šค ๋‹ค์‹œ ์‹œ์ž‘",
				"value": "์•ˆ์ •",
				"concernId": "uuid",
				"concern": "ํ—ฌ์Šค ๋‹ค์‹œ ์‹œ์ž‘ํ• ๊นŒ",
                "recordDate": "2026-07-18",
				"concernStatus": "PENDING|RESOLVED"
		  }
		],
		"recordCount": 8
  }
}

๊ณ ๋ฏผ ํƒ€์ž„๋ผ์ธ ์กฐํšŒ

  • GET /concerns/{concernId}/timeline
  • ๊ธฐ๋ก์€ ์˜ค๋ž˜๋œ ์ˆœ์œผ๋กœ ์ •๋ ฌํ•œ๋‹ค.

Response

{
	"success": true,
	"data": {
		"concern": "ํ—ฌ์Šค ๋‹ค์‹œ ์‹œ์ž‘ํ• ๊นŒ",
		"topic": "๊ฑด๊ฐ•",
		"topicOther": "", // topic์ด ๊ธฐํƒ€์ผ ๊ฒฝ์šฐ ์‚ฌ์šฉ
		"records": [
			{
				"recordId": "uuid",
				"decision": "์˜ค๋Š˜์€ ์‰ฌ์–ด๊ฐ€๊ธฐ",
				"value": "์•ˆ์ •",
				"createdAt": "2026-07-12"
			}
		],
		"concernStatus": "PENDING|RESOLVED",
		"recordCount": 2
	}
}