Skip to content

Latest commit

ย 

History

History
1731 lines (1447 loc) ยท 41.6 KB

File metadata and controls

1731 lines (1447 loc) ยท 41.6 KB

๐Ÿ›ก๏ธ Maple Talk SNS Admin API ๋ช…์„ธ์„œ

๐Ÿ“‹ ๊ฐœ์š”

Maple Talk SNS ๊ด€๋ฆฌ์ž ์„œ๋ฒ„์˜ REST API ๋ช…์„ธ์„œ์ž…๋‹ˆ๋‹ค. ๋ชจ๋“  API๋Š” ์ผ๊ด€๋œ ์‘๋‹ต ํ˜•์‹์„ ๋”ฐ๋ฅด๋ฉฐ, ์›น ์ „์šฉ์œผ๋กœ ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ”„ ๊ณตํ†ต ์‘๋‹ต ํ˜•์‹

โœ… ์„ฑ๊ณต ์‘๋‹ต

{
  "code": 200,
  "message": "์„ฑ๊ณต ๋ฉ”์‹œ์ง€",
  "data": {
    // ์‹ค์ œ ์‘๋‹ต ๋ฐ์ดํ„ฐ
  }
}

โŒ ์—๋Ÿฌ ์‘๋‹ต

{
  "code": 400,
  "message": "์—๋Ÿฌ ๋ฉ”์‹œ์ง€",
  "errors": [
    {
      "field": "ํ•„๋“œ๋ช…",
      "message": "์ƒ์„ธ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€"
    }
  ]
}

๐Ÿ” ๊ด€๋ฆฌ์ž ์ธ์ฆ ๋„๋ฉ”์ธ (Admin Authentication Domain)

๐Ÿ“ฑ ํ”Œ๋žซํผ ์ง€์›

์›น ๋ธŒ๋ผ์šฐ์ € ์ „์šฉ

  • ํ† ํฐ ์ €์žฅ: httpOnly ์ฟ ํ‚ค๋กœ ์ž๋™ ์ €์žฅ
  • ํ† ํฐ ์ „์†ก: ์ฟ ํ‚ค๋กœ ์ž๋™ ์ „์†ก
  • ๋ณด์•ˆ: XSS, CSRF ๊ณต๊ฒฉ ๋ฐฉ์ง€
  • ์‚ฌ์šฉ๋ฒ•: ๋ณ„๋„ ์„ค์ • ์—†์ด ์ž๋™ ์ž‘๋™

โš ๏ธ ์ฃผ์˜: ๋ชจ๋ฐ”์ผ ์•ฑ์—์„œ๋Š” Admin API๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๐ŸŽฏ ๋ณด์•ˆ ํŠน์ง•

  • ๋“ฑ๋ก๋œ Admin๋งŒ ๋กœ๊ทธ์ธ: ๋ฏธ๋ฆฌ ๋“ฑ๋ก๋œ ์นด์นด์˜ค ๊ณ„์ •๋งŒ ํ—ˆ์šฉ
  • ํ† ํฐ ํ•ด์‹œ ์ €์žฅ: RefreshToken ๋ณด์•ˆ ๊ฐ•ํ™”
  • ์—ญํ•  ๊ธฐ๋ฐ˜ ๊ถŒํ•œ: OWNER > ADMIN > CS > READONLY
  • ๊ณ„์ • ์ž ๊ธˆ: ๋กœ๊ทธ์ธ ์‹คํŒจ ํšŸ์ˆ˜ ์ œํ•œ (5ํšŒ)

๐Ÿ“Š ๊ด€๋ฆฌ์ž ์—ญํ•  ๋“ฑ๊ธ‰

์—ญํ•  ๋“ฑ๊ธ‰ ๊ถŒํ•œ ์„ค๋ช…
OWNER 4 ์ตœ๊ณ  ๊ถŒํ•œ (๋ชจ๋“  ๊ธฐ๋Šฅ ์ ‘๊ทผ ๊ฐ€๋Šฅ)
ADMIN 3 ๊ด€๋ฆฌ์ž ๊ถŒํ•œ (๋Œ€๋ถ€๋ถ„ ๊ธฐ๋Šฅ ์ ‘๊ทผ ๊ฐ€๋Šฅ)
CS 2 ๊ณ ๊ฐ์ง€์› ๊ถŒํ•œ (์กฐํšŒ/์ˆ˜์ • ๊ถŒํ•œ)
READONLY 1 ์ฝ๊ธฐ ์ „์šฉ ๊ถŒํ•œ (์กฐํšŒ๋งŒ ๊ฐ€๋Šฅ)

6. ๊ด€๋ฆฌ์ž์šฉ ์‚ฌ์šฉ์ž ๋ชฉ๋ก ์กฐํšŒ (Get Users for Admin)

Endpoint: GET /admin/users

Description: ๊ด€๋ฆฌ์ž๊ฐ€ ์‹œ์Šคํ…œ์— ๋“ฑ๋ก๋œ ๋ชจ๋“  ์‚ฌ์šฉ์ž ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. ์‚ญ์ œ๋˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž๋งŒ ํ‘œ์‹œ๋˜๋ฉฐ ํŽ˜์ด์ง€๋„ค์ด์…˜์ด ์ง€์›๋ฉ๋‹ˆ๋‹ค.

Query Parameters:

ํ•„๋“œ ํƒ€์ž… ํ•„์ˆ˜ ๊ธฐ๋ณธ๊ฐ’ ์„ค๋ช…
page number โŒ 1 ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ (1๋ถ€ํ„ฐ ์‹œ์ž‘)
limit number โŒ 20 ํ•œ ํŽ˜์ด์ง€๋‹น ํ•ญ๋ชฉ ์ˆ˜ (์ตœ๋Œ€ 100)
search string โŒ - ๊ฒ€์ƒ‰์–ด (๋‹‰๋„ค์ž„, ์ด๋ฉ”์ผ์—์„œ ๊ฒ€์ƒ‰)

Request Headers:

Cookie: admin_access={accessToken}

Request Body: ์—†์Œ

๋™์ž‘ ๋ฐฉ์‹:

1. Admin ํ† ํฐ ๊ฒ€์ฆ (adminAuthMiddleware)
2. ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฒ€์ฆ ๋ฐ ๊ธฐ๋ณธ๊ฐ’ ์„ค์ •
3. User ํ…Œ์ด๋ธ”์—์„œ ์‚ญ์ œ๋˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž ์กฐํšŒ
4. ์ด๋ฏธ์ง€ URL์— baseURL ์ถ”๊ฐ€
5. ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ •๋ณด ๊ณ„์‚ฐ
6. ์Šค๋„ค์ดํฌ ์ผ€์ด์Šค ๋ณ€ํ™˜ ํ›„ ์‘๋‹ต

Response (200 OK):

{
  "code": 200,
  "message": "์‚ฌ์šฉ์ž ๋ชฉ๋ก์„ ์กฐํšŒํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "data": {
    "users": [
      {
        "id": 1,
        "email": "user@example.com",
        "nickname": "์‚ฌ์šฉ์ž๋‹‰๋„ค์ž„",
        "profile_img": "https://sa-server-u2xi.onrender.com/uploads/profile/image.jpg",
        "bio": "์ž๊ธฐ์†Œ๊ฐœ ํ…์ŠคํŠธ",
        "profile_visibility": "public",
        "is_deleted": 0,
        "created_at": "2024-01-01T00:00:00.000Z",
        "updated_at": "2024-01-01T00:00:00.000Z",
        "login_type": "social",
        "providers": ["kakao"]
      },
      {
        "id": 2,
        "email": "local@example.com",
        "nickname": "๋กœ์ปฌ์‚ฌ์šฉ์ž",
        "profile_img": null,
        "bio": null,
        "profile_visibility": "public",
        "is_deleted": 0,
        "created_at": "2024-01-02T00:00:00.000Z",
        "updated_at": "2024-01-02T00:00:00.000Z",
        "login_type": "local",
        "providers": []
      }
    ],
    "pagination": {
      "current_page": 1,
      "total_pages": 5,
      "total_count": 50,
      "per_page": 10,
      "has_next": true,
      "has_prev": false
    }
  }
}

ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ƒ์„ธ:

  • current_page: ํ˜„์žฌ ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ
  • total_pages: ์ „์ฒด ํŽ˜์ด์ง€ ์ˆ˜
  • total_count: ์ „์ฒด ์‚ฌ์šฉ์ž ์ˆ˜
  • per_page: ํ•œ ํŽ˜์ด์ง€๋‹น ํ•ญ๋ชฉ ์ˆ˜
  • has_next: ๋‹ค์Œ ํŽ˜์ด์ง€ ์กด์žฌ ์—ฌ๋ถ€
  • has_prev: ์ด์ „ ํŽ˜์ด์ง€ ์กด์žฌ ์—ฌ๋ถ€

ํ•„ํ„ฐ๋ง:

  • is_deleted = 0 โ†’ ์‚ญ์ œ๋˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž๋งŒ ํ‘œ์‹œ
  • ์ •๋ ฌ: created_at DESC (์ตœ์‹  ์‚ฌ์šฉ์ž ์šฐ์„ )

์ด๋ฏธ์ง€ URL ์ฒ˜๋ฆฌ:

  • DB์— ์ €์žฅ๋œ ์ƒ๋Œ€ ๊ฒฝ๋กœ๋ฅผ ์™„์ „ํ•œ URL๋กœ ๋ณ€ํ™˜
  • profile_img: "/uploads/profile/image.jpg" โ†’ "https://sa-server-u2xi.onrender.com/uploads/profile/image.jpg"

Error Responses:

401 Unauthorized - ์ธ์ฆ ์‹คํŒจ:

{
  "code": 401,
  "message": "๊ด€๋ฆฌ์ž ์ธ์ฆ ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.",
  "errors": []
}

500 Internal Server Error:

{
  "code": 500,
  "message": "์‚ฌ์šฉ์ž ๋ชฉ๋ก ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "errors": []
}

7. ๊ด€๋ฆฌ์ž์šฉ ๋ฌธ์˜ ๋‚ด์—ญ ์กฐํšŒ (Get Inquiries for Admin)

Endpoint: GET /admin/inquiries

Description: ๊ด€๋ฆฌ์ž๊ฐ€ ์‹œ์Šคํ…œ์— ๋“ฑ๋ก๋œ ๋ชจ๋“  ๋ฌธ์˜ ๋‚ด์—ญ์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. ํŽ˜์ด์ง€๋„ค์ด์…˜๊ณผ ํ•„ํ„ฐ๋ง์ด ์ง€์›๋ฉ๋‹ˆ๋‹ค.

Query Parameters:

ํ•„๋“œ ํƒ€์ž… ํ•„์ˆ˜ ๊ธฐ๋ณธ๊ฐ’ ์„ค๋ช…
page number โŒ 1 ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ (1๋ถ€ํ„ฐ ์‹œ์ž‘)
limit number โŒ 20 ํ•œ ํŽ˜์ด์ง€๋‹น ํ•ญ๋ชฉ ์ˆ˜ (์ตœ๋Œ€ 100)
status string โŒ - ์ƒํƒœ ํ•„ํ„ฐ (pending, in_progress, resolved, closed)
priority string โŒ - ์šฐ์„ ์ˆœ์œ„ ํ•„ํ„ฐ (low, normal, high, urgent)
search string โŒ - ๊ฒ€์ƒ‰์–ด (์ œ๋ชฉ, ๋‚ด์šฉ์—์„œ ๊ฒ€์ƒ‰)

Request Headers:

Cookie: admin_access={accessToken}

Request Body: ์—†์Œ

๋™์ž‘ ๋ฐฉ์‹:

1. Admin ํ† ํฐ ๊ฒ€์ฆ (adminAuthMiddleware)
2. ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฒ€์ฆ ๋ฐ ํ•„ํ„ฐ๋ง ์ ์šฉ
3. Inquiry์™€ User, InquiryAttachment LEFT JOIN
4. ์ฒจ๋ถ€ํŒŒ์ผ URL์— baseURL ์ถ”๊ฐ€
5. ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ •๋ณด ๊ณ„์‚ฐ
6. ์Šค๋„ค์ดํฌ ์ผ€์ด์Šค ๋ณ€ํ™˜ ํ›„ ์‘๋‹ต

Response (200 OK):

{
  "code": 200,
  "message": "๋ฌธ์˜ ๋‚ด์—ญ์„ ์กฐํšŒํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "data": {
    "inquiries": [
      {
        "id": 1,
        "user": {
          "id": 10,
          "nickname": "์‚ฌ์šฉ์ž๋‹‰๋„ค์ž„",
          "email": "user@example.com"
        },
        "category": "๊ณ„์ •๋ฌธ์ œ",
        "title": "๋กœ๊ทธ์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
        "content": "๋กœ๊ทธ์ธ์ด ์•ˆ ๋ผ์š”...",
        "status": "pending",
        "priority": "normal",
        "attachments": [
          {
            "id": 1,
            "filename": "error_screenshot.png",
            "file_url": "https://sa-server-u2xi.onrender.com/uploads/inquiry/error_screenshot.png"
          }
        ],
        "created_at": "2024-01-01T00:00:00.000Z",
        "updated_at": "2024-01-01T00:00:00.000Z"
      }
    ],
    "pagination": {
      "current_page": 1,
      "total_pages": 5,
      "total_count": 50,
      "per_page": 10,
      "has_next": true,
      "has_prev": false
    }
  }
}

ํ•„ํ„ฐ๋ง ์˜ˆ์‹œ:

# ๋Œ€๊ธฐ ์ค‘์ธ ๋ฌธ์˜๋งŒ ์กฐํšŒ
GET /admin/inquiries?status=pending

# ๊ธด๊ธ‰ ์šฐ์„ ์ˆœ์œ„ ๋ฌธ์˜ ์กฐํšŒ
GET /admin/inquiries?priority=urgent

# ์ฒ˜๋ฆฌ ์ค‘์ธ ๋ฌธ์˜, 2ํŽ˜์ด์ง€
GET /admin/inquiries?status=in_progress&page=2&limit=10

JOIN ์ •๋ณด:

  • User: ๋ฌธ์˜ํ•œ ์‚ฌ์šฉ์ž ์ •๋ณด (INNER JOIN)
  • InquiryAttachment: ์ฒจ๋ถ€ํŒŒ์ผ ์ •๋ณด (LEFT JOIN)

์ด๋ฏธ์ง€ URL ์ฒ˜๋ฆฌ:

  • ์ฒจ๋ถ€ํŒŒ์ผ์˜ ์ƒ๋Œ€ ๊ฒฝ๋กœ๋ฅผ ์™„์ „ํ•œ URL๋กœ ๋ณ€ํ™˜

์ •๋ ฌ:

  • created_at DESC (์ตœ์‹  ๋ฌธ์˜ ์šฐ์„ )

Error Responses:

401 Unauthorized - ์ธ์ฆ ์‹คํŒจ:

{
  "code": 401,
  "message": "๊ด€๋ฆฌ์ž ์ธ์ฆ ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.",
  "errors": []
}

500 Internal Server Error:

{
  "code": 500,
  "message": "๋ฌธ์˜ ๋‚ด์—ญ ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "errors": []
}

8. ๊ด€๋ฆฌ์ž์šฉ ๊ฒŒ์‹œ๋ฌผ ๋ชฉ๋ก ์กฐํšŒ (Get Posts for Admin)

Endpoint: GET /admin/posts

Description: ๊ด€๋ฆฌ์ž๊ฐ€ ์‹œ์Šคํ…œ์— ๋“ฑ๋ก๋œ ๋ชจ๋“  ๊ฒŒ์‹œ๋ฌผ ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. ํŽ˜์ด์ง€๋„ค์ด์…˜๊ณผ ํ•„ํ„ฐ๋ง์ด ์ง€์›๋ฉ๋‹ˆ๋‹ค.

Query Parameters:

ํ•„๋“œ ํƒ€์ž… ํ•„์ˆ˜ ๊ธฐ๋ณธ๊ฐ’ ์„ค๋ช…
page number โŒ 1 ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ (1๋ถ€ํ„ฐ ์‹œ์ž‘)
limit number โŒ 20 ํ•œ ํŽ˜์ด์ง€๋‹น ํ•ญ๋ชฉ ์ˆ˜ (์ตœ๋Œ€ 100)
category string โŒ - ์ƒ์œ„ ์นดํ…Œ๊ณ ๋ฆฌ ID ํ•„ํ„ฐ
sub_category string โŒ - ์„œ๋ธŒ ์นดํ…Œ๊ณ ๋ฆฌ ID ํ•„ํ„ฐ
search string โŒ - ์ œ๋ชฉ ๊ฒ€์ƒ‰

Request Headers:

Cookie: admin_access={accessToken}

Request Body: ์—†์Œ

๋™์ž‘ ๋ฐฉ์‹:

1. Admin ํ† ํฐ ๊ฒ€์ฆ (adminAuthMiddleware)
2. ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฒ€์ฆ ๋ฐ ํ•„ํ„ฐ๋ง ์ ์šฉ
3. Post์™€ User, SubCategory, Category JOIN
4. ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ •๋ณด ๊ณ„์‚ฐ
5. ์Šค๋„ค์ดํฌ ์ผ€์ด์Šค ๋ณ€ํ™˜ ํ›„ ์‘๋‹ต

Response (200 OK):

{
  "code": 200,
  "message": "๊ฒŒ์‹œ๋ฌผ ๋ชฉ๋ก์„ ์กฐํšŒํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "data": {
    "posts": [
      {
        "id": 1,
        "title": "๊ฒŒ์‹œ๋ฌผ ์ œ๋ชฉ์ž…๋‹ˆ๋‹ค",
        "user": {
          "id": 10,
          "nickname": "์ž‘์„ฑ์ž๋‹‰๋„ค์ž„",
          "email": "user@example.com"
        },
        "category": {
          "id": 1,
          "name": "์นดํ…Œ๊ณ ๋ฆฌ๋ช…",
          "sub_category": {
            "id": 5,
            "name": "์„œ๋ธŒ์นดํ…Œ๊ณ ๋ฆฌ๋ช…"
          }
        },
        "created_at": "2024-01-01T00:00:00.000Z",
        "updated_at": "2024-01-01T00:00:00.000Z"
      }
    ],
    "pagination": {
      "current_page": 1,
      "total_pages": 5,
      "total_count": 50,
      "per_page": 10,
      "has_next": true,
      "has_prev": false
    }
  }
}

ํ•„ํ„ฐ๋ง ์˜ˆ์‹œ:

# ํŠน์ • ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๊ฒŒ์‹œ๋ฌผ๋งŒ ์กฐํšŒ
GET /admin/posts?category=1

# ํŠน์ • ์„œ๋ธŒ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๊ฒŒ์‹œ๋ฌผ ์กฐํšŒ
GET /admin/posts?sub_category=5

# ์ œ๋ชฉ ๊ฒ€์ƒ‰
GET /admin/posts?search=๊ฒŒ์‹œ๋ฌผ

# ๋ณตํ•ฉ ํ•„ํ„ฐ๋ง
GET /admin/posts?category=1&search=์งˆ๋ฌธ&page=2&limit=10

JOIN ์ •๋ณด:

  • User: ๊ฒŒ์‹œ๋ฌผ ์ž‘์„ฑ์ž ์ •๋ณด (INNER JOIN)
  • SubCategory: ์„œ๋ธŒ ์นดํ…Œ๊ณ ๋ฆฌ ์ •๋ณด (INNER JOIN)
  • Category: ์ƒ์œ„ ์นดํ…Œ๊ณ ๋ฆฌ ์ •๋ณด (INNER JOIN, SubCategory๋ฅผ ํ†ตํ•ด)

์ •๋ ฌ:

  • created_at DESC (์ตœ์‹  ๊ฒŒ์‹œ๋ฌผ ์šฐ์„ )

Error Responses:

401 Unauthorized - ์ธ์ฆ ์‹คํŒจ:

{
  "code": 401,
  "message": "๊ด€๋ฆฌ์ž ์ธ์ฆ ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.",
  "errors": []
}

500 Internal Server Error:

{
  "code": 500,
  "message": "๊ฒŒ์‹œ๋ฌผ ๋ชฉ๋ก ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "errors": []
}

9. ๊ด€๋ฆฌ์ž์šฉ ๊ฒŒ์‹œ๋ฌผ ์ƒ์„ธ ์กฐํšŒ (Get Post Detail for Admin)

Endpoint: GET /admin/posts/:postId

Description: ๊ด€๋ฆฌ์ž๊ฐ€ ํŠน์ • ๊ฒŒ์‹œ๋ฌผ์˜ ์ƒ์„ธ ์ •๋ณด๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. ๊ฒŒ์‹œ๋ฌผ ๋‚ด์šฉ, ์ž‘์„ฑ์ž ์ •๋ณด, ์นดํ…Œ๊ณ ๋ฆฌ ์ •๋ณด๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

Path Parameters:

ํ•„๋“œ ํƒ€์ž… ํ•„์ˆ˜ ์„ค๋ช…
postId number โœ… ๊ฒŒ์‹œ๋ฌผ ID

Request Headers:

Cookie: admin_access={accessToken}

Request Body: ์—†์Œ

๋™์ž‘ ๋ฐฉ์‹:

1. Admin ํ† ํฐ ๊ฒ€์ฆ (adminAuthMiddleware)
2. postId ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฒ€์ฆ
3. Post์™€ User, SubCategory, Category, PostContent JOIN
4. ์ด๋ฏธ์ง€/๋น„๋””์˜ค URL์— baseURL ์ถ”๊ฐ€
5. ์Šค๋„ค์ดํฌ ์ผ€์ด์Šค ๋ณ€ํ™˜ ํ›„ ์‘๋‹ต

Response (200 OK):

{
  "code": 200,
  "message": "๊ฒŒ์‹œ๋ฌผ์„ ์กฐํšŒํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "data": {
    "id": 1,
    "title": "๊ฒŒ์‹œ๋ฌผ ์ œ๋ชฉ์ž…๋‹ˆ๋‹ค",
    "user": {
      "id": 10,
      "nickname": "์ž‘์„ฑ์ž๋‹‰๋„ค์ž„",
      "email": "user@example.com"
    },
    "category": {
      "id": 1,
      "name": "์นดํ…Œ๊ณ ๋ฆฌ๋ช…",
      "sub_category": {
        "id": 5,
        "name": "์„œ๋ธŒ์นดํ…Œ๊ณ ๋ฆฌ๋ช…"
      }
    },
    "content_blocks": [
      {
        "type": "text",
        "value": "ํ…์ŠคํŠธ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.",
        "sequence": 1
      },
      {
        "type": "image",
        "value": "https://sa-server-u2xi.onrender.com/uploads/post/image.jpg",
        "sequence": 2
      },
      {
        "type": "video",
        "value": "https://sa-server-u2xi.onrender.com/uploads/post/video.mp4",
        "thumbnail_url": "https://sa-server-u2xi.onrender.com/uploads/post/thumbnail.jpg",
        "sequence": 3
      }
    ],
    "created_at": "2024-01-01T00:00:00.000Z",
    "updated_at": "2024-01-01T00:00:00.000Z"
  }
}

JOIN ์ •๋ณด:

  • User: ๊ฒŒ์‹œ๋ฌผ ์ž‘์„ฑ์ž ์ •๋ณด (INNER JOIN)
  • SubCategory: ์„œ๋ธŒ ์นดํ…Œ๊ณ ๋ฆฌ ์ •๋ณด (INNER JOIN)
  • Category: ์ƒ์œ„ ์นดํ…Œ๊ณ ๋ฆฌ ์ •๋ณด (INNER JOIN, SubCategory๋ฅผ ํ†ตํ•ด)
  • PostContent: ๊ฒŒ์‹œ๋ฌผ ๋‚ด์šฉ ๋ธ”๋ก๋“ค (๋ณ„๋„ ์กฐํšŒ)

์ด๋ฏธ์ง€ URL ์ฒ˜๋ฆฌ:

  • DB์— ์ €์žฅ๋œ ์ƒ๋Œ€ ๊ฒฝ๋กœ๋ฅผ ์™„์ „ํ•œ URL๋กœ ๋ณ€ํ™˜
  • content_blocks[].value: ํ…์ŠคํŠธ ๋˜๋Š” ์ด๋ฏธ์ง€/๋น„๋””์˜ค ๊ฒฝ๋กœ
  • content_blocks[].thumbnail_url: ๋น„๋””์˜ค ์ธ๋„ค์ผ ๊ฒฝ๋กœ

Error Responses:

400 Bad Request - ์ž˜๋ชป๋œ postId:

{
  "code": 400,
  "message": "์˜ฌ๋ฐ”๋ฅธ ๊ฒŒ์‹œ๋ฌผ ID๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.",
  "errors": []
}

401 Unauthorized - ์ธ์ฆ ์‹คํŒจ:

{
  "code": 401,
  "message": "๊ด€๋ฆฌ์ž ์ธ์ฆ ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.",
  "errors": []
}

404 Not Found - ๊ฒŒ์‹œ๋ฌผ ์—†์Œ:

{
  "code": 404,
  "message": "๊ฒŒ์‹œ๋ฌผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.",
  "errors": []
}

500 Internal Server Error:

{
  "code": 500,
  "message": "๊ฒŒ์‹œ๋ฌผ ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "errors": []
}

10. ๊ด€๋ฆฌ์ž์šฉ ๊ฒŒ์‹œ๋ฌผ ์‚ญ์ œ (Delete Post for Admin)

Endpoint: DELETE /admin/posts/:postId

Description: ๊ด€๋ฆฌ์ž๊ฐ€ ํŠน์ • ๊ฒŒ์‹œ๋ฌผ์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. ๊ฒŒ์‹œ๋ฌผ๊ณผ ๊ด€๋ จ๋œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ(์ฝ˜ํ…์ธ , ์ข‹์•„์š”, ๋ถ๋งˆํฌ, ๋Œ“๊ธ€ ๋“ฑ)๋ฅผ ํ•จ๊ป˜ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.

Path Parameters:

ํ•„๋“œ ํƒ€์ž… ํ•„์ˆ˜ ์„ค๋ช…
postId number โœ… ์‚ญ์ œํ•  ๊ฒŒ์‹œ๋ฌผ ID

Request Headers:

Cookie: admin_access={accessToken}

Request Body: ์—†์Œ

๋™์ž‘ ๋ฐฉ์‹:

1. Admin ํ† ํฐ ๊ฒ€์ฆ (adminAuthMiddleware)
2. postId ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฒ€์ฆ
3. ๊ฒŒ์‹œ๋ฌผ ์กด์žฌ ํ™•์ธ
4. ํŠธ๋žœ์žญ์…˜ ์‹œ์ž‘
5. PostContent ์‚ญ์ œ
6. PostLike ์‚ญ์ œ
7. PostBookmark ์‚ญ์ œ
8. Comment ๋ฐ CommentLike ์‚ญ์ œ
9. Post ์‚ญ์ œ
10. ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹

Response (200 OK):

{
  "code": 200,
  "message": "๊ฒŒ์‹œ๋ฌผ์ด ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
  "data": {
    "success": true
  }
}

์‚ญ์ œ๋˜๋Š” ๋ฐ์ดํ„ฐ:

  • ๊ฒŒ์‹œ๋ฌผ (Post)
  • ๊ฒŒ์‹œ๋ฌผ ๋‚ด์šฉ (PostContent)
  • ์ข‹์•„์š” (PostLike)
  • ๋ถ๋งˆํฌ (PostBookmark)
  • ๋Œ“๊ธ€ (Comment)
  • ๋Œ“๊ธ€ ์ข‹์•„์š” (CommentLike)

Error Responses:

400 Bad Request - ์ž˜๋ชป๋œ postId:

{
  "code": 400,
  "message": "์˜ฌ๋ฐ”๋ฅธ ๊ฒŒ์‹œ๋ฌผ ID๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.",
  "errors": []
}

401 Unauthorized - ์ธ์ฆ ์‹คํŒจ:

{
  "code": 401,
  "message": "๊ด€๋ฆฌ์ž ์ธ์ฆ ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.",
  "errors": []
}

404 Not Found - ๊ฒŒ์‹œ๋ฌผ ์—†์Œ:

{
  "code": 404,
  "message": "๊ฒŒ์‹œ๋ฌผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.",
  "errors": []
}

500 Internal Server Error:

{
  "code": 500,
  "message": "๊ฒŒ์‹œ๋ฌผ ์‚ญ์ œ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "errors": []
}

11. ๊ด€๋ฆฌ์ž์šฉ ํ”ผ๋“œ ๋ชฉ๋ก ์กฐํšŒ (Get Feeds for Admin - All Feeds)

Endpoint: GET /admin/feeds

Description: ๊ด€๋ฆฌ์ž๊ฐ€ ์‹œ์Šคํ…œ์— ๋“ฑ๋ก๋œ ๋ชจ๋“  ํ”ผ๋“œ ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ์กด ์‚ฌ์šฉ์ž ํ”ผ๋“œ ์กฐํšŒ์™€ ๋‹ฌ๋ฆฌ ํŒ”๋กœ์›Œ ์ œํ•œ ์—†์ด ๋ชจ๋“  ํ”ผ๋“œ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ํŽ˜์ด์ง€๋„ค์ด์…˜๊ณผ ํ•„ํ„ฐ๋ง์ด ์ง€์›๋ฉ๋‹ˆ๋‹ค.

Query Parameters:

ํ•„๋“œ ํƒ€์ž… ํ•„์ˆ˜ ๊ธฐ๋ณธ๊ฐ’ ์„ค๋ช…
page number โŒ 1 ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ (1๋ถ€ํ„ฐ ์‹œ์ž‘)
limit number โŒ 20 ํ•œ ํŽ˜์ด์ง€๋‹น ํ•ญ๋ชฉ ์ˆ˜ (์ตœ๋Œ€ 100)
user_id string โŒ - ํŠน์ • ์‚ฌ์šฉ์ž ํ”ผ๋“œ ํ•„ํ„ฐ
search string โŒ - ๋‚ด์šฉ ๊ฒ€์ƒ‰ (ํ…์ŠคํŠธ ๋ธ”๋ก์—์„œ ๊ฒ€์ƒ‰)

Request Headers:

Cookie: admin_access={accessToken}

Request Body: ์—†์Œ

๋™์ž‘ ๋ฐฉ์‹:

1. Admin ํ† ํฐ ๊ฒ€์ฆ (adminAuthMiddleware)
2. ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฒ€์ฆ ๋ฐ ํ•„ํ„ฐ๋ง ์ ์šฉ
3. FeedPost์™€ User JOIN (ํŒ”๋กœ์›Œ ์ œํ•œ ์—†์Œ)
4. ๊ฐ ํ”ผ๋“œ์˜ FeedPostContent ์กฐํšŒ ๋ฐ URL ์™„์„ฑ
5. ๊ฒ€์ƒ‰ ํ•„ํ„ฐ ์ ์šฉ (content_blocks ํ…์ŠคํŠธ ๊ฒ€์ƒ‰)
6. ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ •๋ณด ๊ณ„์‚ฐ
7. ์Šค๋„ค์ดํฌ ์ผ€์ด์Šค ๋ณ€ํ™˜ ํ›„ ์‘๋‹ต

Response (200 OK):

{
  "code": 200,
  "message": "ํ”ผ๋“œ ๋ชฉ๋ก์„ ์กฐํšŒํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "data": {
    "feeds": [
      {
        "id": 1,
        "user": {
          "id": 10,
          "nickname": "์ž‘์„ฑ์ž๋‹‰๋„ค์ž„",
          "email": "user@example.com"
        },
        "content_blocks": [
          {
            "type": "text",
            "value": "ํ”ผ๋“œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.",
            "sequence": 1
          },
          {
            "type": "image",
            "value": "https://sa-server-u2xi.onrender.com/uploads/feed/image.jpg",
            "sequence": 2
          },
          {
            "type": "video",
            "value": "https://sa-server-u2xi.onrender.com/uploads/feed/video.mp4",
            "thumbnail_url": "https://sa-server-u2xi.onrender.com/uploads/feed/thumbnail.jpg",
            "sequence": 3
          }
        ],
        "created_at": "2024-01-01T00:00:00.000Z",
        "updated_at": "2024-01-01T00:00:00.000Z"
      }
    ],
    "pagination": {
      "current_page": 1,
      "total_pages": 5,
      "total_count": 50,
      "per_page": 10,
      "has_next": true,
      "has_prev": false
    }
  }
}

ํ•„ํ„ฐ๋ง ์˜ˆ์‹œ:

# ํŠน์ • ์‚ฌ์šฉ์ž์˜ ํ”ผ๋“œ๋งŒ ์กฐํšŒ
GET /admin/feeds?user_id=10

# ๋‚ด์šฉ ๊ฒ€์ƒ‰
GET /admin/feeds?search=์•ˆ๋…•ํ•˜์„ธ์š”

# ๋ณตํ•ฉ ํ•„ํ„ฐ๋ง
GET /admin/feeds?user_id=10&search=์‚ฌ์ง„&page=2&limit=10

JOIN ์ •๋ณด:

  • User: ํ”ผ๋“œ ์ž‘์„ฑ์ž ์ •๋ณด (INNER JOIN)

๊ฒ€์ƒ‰ ๋Œ€์ƒ:

  • content_blocks์˜ type: "text"์ธ ๋ธ”๋ก๋“ค์˜ value ๋‚ด์šฉ

์ด๋ฏธ์ง€ URL ์ฒ˜๋ฆฌ:

  • DB์— ์ €์žฅ๋œ ์ƒ๋Œ€ ๊ฒฝ๋กœ๋ฅผ ์™„์ „ํ•œ URL๋กœ ๋ณ€ํ™˜
  • content_blocks[].value: ํ…์ŠคํŠธ ๋˜๋Š” ์ด๋ฏธ์ง€/๋น„๋””์˜ค ๊ฒฝ๋กœ
  • content_blocks[].thumbnail_url: ๋น„๋””์˜ค ์ธ๋„ค์ผ ๊ฒฝ๋กœ

์ฐจ์ด์  (vs ์ผ๋ฐ˜ ํ”ผ๋“œ ์กฐํšŒ):

  • ํŒ”๋กœ์›Œ ์ œํ•œ ์—†์Œ (๋ชจ๋“  ํ”ผ๋“œ ํ‘œ์‹œ)
  • ๊ด€๋ฆฌ์ž ์ „์šฉ ํ•„ํ„ฐ๋ง ์˜ต์…˜
  • ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ ํฌํ•จ

์ •๋ ฌ:

  • created_at DESC (์ตœ์‹  ํ”ผ๋“œ ์šฐ์„ )

Error Responses:

401 Unauthorized - ์ธ์ฆ ์‹คํŒจ:

{
  "code": 401,
  "message": "๊ด€๋ฆฌ์ž ์ธ์ฆ ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.",
  "errors": []
}

500 Internal Server Error:

{
  "code": 500,
  "message": "ํ”ผ๋“œ ๋ชฉ๋ก ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "errors": []
}

12. ๊ด€๋ฆฌ์ž์šฉ ํ”ผ๋“œ ์‚ญ์ œ (Delete Feed for Admin)

Endpoint: DELETE /admin/feeds/:feedId

Description: ๊ด€๋ฆฌ์ž๊ฐ€ ํŠน์ • ํ”ผ๋“œ๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. ํ”ผ๋“œ์™€ ๊ด€๋ จ๋œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ(์ฝ˜ํ…์ธ , ์ข‹์•„์š”, ๋ถ๋งˆํฌ, ๋Œ“๊ธ€ ๋“ฑ)๋ฅผ ํ•จ๊ป˜ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.

Path Parameters:

ํ•„๋“œ ํƒ€์ž… ํ•„์ˆ˜ ์„ค๋ช…
feedId number โœ… ์‚ญ์ œํ•  ํ”ผ๋“œ ID

Request Headers:

Cookie: admin_access={accessToken}

Request Body: ์—†์Œ

๋™์ž‘ ๋ฐฉ์‹:

1. Admin ํ† ํฐ ๊ฒ€์ฆ (adminAuthMiddleware)
2. feedId ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฒ€์ฆ
3. ํ”ผ๋“œ ์กด์žฌ ํ™•์ธ
4. ํŠธ๋žœ์žญ์…˜ ์‹œ์ž‘
5. FeedPostContent ์‚ญ์ œ
6. FeedLike ์‚ญ์ œ
7. FeedBookmark ์‚ญ์ œ
8. FeedComment ๋ฐ FeedCommentLike ์‚ญ์ œ
9. FeedPost ์‚ญ์ œ
10. ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹

Response (200 OK):

{
  "code": 200,
  "message": "ํ”ผ๋“œ๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
  "data": {
    "success": true
  }
}

์‚ญ์ œ๋˜๋Š” ๋ฐ์ดํ„ฐ:

  • ํ”ผ๋“œ (FeedPost)
  • ํ”ผ๋“œ ๋‚ด์šฉ (FeedPostContent)
  • ์ข‹์•„์š” (FeedLike)
  • ๋ถ๋งˆํฌ (FeedBookmark)
  • ๋Œ“๊ธ€ (FeedComment)
  • ๋Œ“๊ธ€ ์ข‹์•„์š” (FeedCommentLike)

Error Responses:

400 Bad Request - ์ž˜๋ชป๋œ feedId:

{
  "code": 400,
  "message": "์˜ฌ๋ฐ”๋ฅธ ํ”ผ๋“œ ID๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.",
  "errors": []
}

401 Unauthorized - ์ธ์ฆ ์‹คํŒจ:

{
  "code": 401,
  "message": "๊ด€๋ฆฌ์ž ์ธ์ฆ ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.",
  "errors": []
}

404 Not Found - ํ”ผ๋“œ ์—†์Œ:

{
  "code": 404,
  "message": "ํ”ผ๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.",
  "errors": []
}

500 Internal Server Error:

{
  "code": 500,
  "message": "ํ”ผ๋“œ ์‚ญ์ œ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "errors": []
}

13. ๊ด€๋ฆฌ์ž์šฉ ์‹ ๊ณ  ๋ชฉ๋ก ์กฐํšŒ (Get Reports for Admin)

Endpoint: GET /admin/reports

Description: ๊ด€๋ฆฌ์ž๊ฐ€ ์‹œ์Šคํ…œ์— ๋“ฑ๋ก๋œ ๋ชจ๋“  ์‹ ๊ณ  ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. ํŽ˜์ด์ง€๋„ค์ด์…˜๊ณผ ํ•„ํ„ฐ๋ง์ด ์ง€์›๋ฉ๋‹ˆ๋‹ค.

Query Parameters:

ํ•„๋“œ ํƒ€์ž… ํ•„์ˆ˜ ๊ธฐ๋ณธ๊ฐ’ ์„ค๋ช…
page number โŒ 1 ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ (1๋ถ€ํ„ฐ ์‹œ์ž‘)
limit number โŒ 20 ํ•œ ํŽ˜์ด์ง€๋‹น ํ•ญ๋ชฉ ์ˆ˜ (์ตœ๋Œ€ 100)
status string โŒ - ์ƒํƒœ ํ•„ํ„ฐ (pending, resolved, dismissed)
target_type string โŒ - ๋Œ€์ƒ ํƒ€์ž… ํ•„ํ„ฐ (post, feed_post, comment, feed_comment, short, short_comment, user, chat_room, message, story)
category string โŒ - ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ (spam, abuse, nudity, fraud, copyright, harassment, hate_speech, other)

Request Headers:

Cookie: admin_access={accessToken}

Request Body: ์—†์Œ

๋™์ž‘ ๋ฐฉ์‹:

1. Admin ํ† ํฐ ๊ฒ€์ฆ (adminAuthMiddleware)
2. ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฒ€์ฆ ๋ฐ ๊ธฐ๋ณธ๊ฐ’ ์„ค์ •
3. Report์™€ User, Admin LEFT JOIN
4. ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ •๋ณด ๊ณ„์‚ฐ
5. ์Šค๋„ค์ดํฌ ์ผ€์ด์Šค ๋ณ€ํ™˜ ํ›„ ์‘๋‹ต

Response (200 OK):

{
  "code": 200,
  "message": "์‹ ๊ณ  ๋ชฉ๋ก์„ ์กฐํšŒํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "data": {
    "reports": [
      {
        "id": 1,
        "reporter_id": 10,
        "reporter_nickname": "์‹ ๊ณ ์ž๋‹‰๋„ค์ž„",
        "target_type": "post",
        "target_id": 100,
        "category": "spam",
        "reason": "์ŠคํŒธ ๊ฒŒ์‹œ๋ฌผ์ž…๋‹ˆ๋‹ค",
        "status": "pending",
        "admin_id": null,
        "admin_nickname": null,
        "admin_note": null,
        "resolved_at": null,
        "target_snapshot": {
          "target_type": "post",
          "target_id": 100,
          "created_at": "2024-01-01T00:00:00.000Z"
        },
        "created_at": "2024-01-01T00:00:00.000Z",
        "updated_at": "2024-01-01T00:00:00.000Z"
      }
    ],
    "pagination": {
      "current_page": 1,
      "total_pages": 5,
      "total_count": 50,
      "per_page": 10,
      "has_next": true,
      "has_prev": false
    }
  }
}

ํ•„ํ„ฐ๋ง ์˜ˆ์‹œ:

# ๋Œ€๊ธฐ ์ค‘์ธ ์‹ ๊ณ ๋งŒ ์กฐํšŒ
GET /admin/reports?status=pending

# ํŠน์ • ๋Œ€์ƒ ํƒ€์ž…์˜ ์‹ ๊ณ  ์กฐํšŒ
GET /admin/reports?target_type=post

# ์ŠคํŒธ ์นดํ…Œ๊ณ ๋ฆฌ ์‹ ๊ณ  ์กฐํšŒ
GET /admin/reports?category=spam

# ๋ณตํ•ฉ ํ•„ํ„ฐ๋ง
GET /admin/reports?status=pending&target_type=post&category=spam&page=2&limit=10

JOIN ์ •๋ณด:

  • User: ์‹ ๊ณ ์ž ์ •๋ณด (INNER JOIN)
  • Admin: ์ฒ˜๋ฆฌํ•œ ๊ด€๋ฆฌ์ž ์ •๋ณด (LEFT JOIN)

์ •๋ ฌ:

  • created_at DESC (์ตœ์‹  ์‹ ๊ณ  ์šฐ์„ )

Error Responses:

401 Unauthorized - ์ธ์ฆ ์‹คํŒจ:

{
  "code": 401,
  "message": "๊ด€๋ฆฌ์ž ์ธ์ฆ ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.",
  "errors": []
}

500 Internal Server Error:

{
  "code": 500,
  "message": "์‹ ๊ณ  ๋ชฉ๋ก ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "errors": []
}

14. ๊ด€๋ฆฌ์ž์šฉ ์‹ ๊ณ  ํ†ต๊ณ„ ์กฐํšŒ (Get Report Stats)

Endpoint: GET /admin/reports/stats

Description: ๊ด€๋ฆฌ์ž๊ฐ€ ์‹ ๊ณ ์— ๋Œ€ํ•œ ํ†ต๊ณ„ ์ •๋ณด๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. ์ƒํƒœ๋ณ„, ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„, ๋Œ€์ƒ ํƒ€์ž…๋ณ„ ํ†ต๊ณ„๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

Request Headers:

Cookie: admin_access={accessToken}

Request Body: ์—†์Œ

๋™์ž‘ ๋ฐฉ์‹:

1. Admin ํ† ํฐ ๊ฒ€์ฆ (adminAuthMiddleware)
2. Report ํ…Œ์ด๋ธ”์—์„œ GROUP BY ์ง‘๊ณ„ ์ฟผ๋ฆฌ ์‹คํ–‰
3. ์ƒํƒœ๋ณ„, ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„, ๋Œ€์ƒ ํƒ€์ž…๋ณ„ ํ†ต๊ณ„ ๊ณ„์‚ฐ
4. ์Šค๋„ค์ดํฌ ์ผ€์ด์Šค ๋ณ€ํ™˜ ํ›„ ์‘๋‹ต

Response (200 OK):

{
  "code": 200,
  "message": "์‹ ๊ณ  ํ†ต๊ณ„๋ฅผ ์กฐํšŒํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "data": {
    "total_reports": 150,
    "pending_reports": 80,
    "resolved_reports": 50,
    "dismissed_reports": 20,
    "reports_by_category": {
      "spam": 60,
      "abuse": 30,
      "nudity": 20,
      "fraud": 15,
      "copyright": 10,
      "harassment": 10,
      "hate_speech": 5,
      "other": 10
    },
    "reports_by_target_type": {
      "post": 50,
      "feed_post": 30,
      "comment": 25,
      "feed_comment": 20,
      "short": 15,
      "short_comment": 10,
      "user": 5,
      "chat_room": 3,
      "message": 1,
      "story": 1
    }
  }
}

ํ†ต๊ณ„ ํ•ญ๋ชฉ ์„ค๋ช…:

  • total_reports: ์ด ์‹ ๊ณ  ์ˆ˜
  • pending_reports: ๋Œ€๊ธฐ ์ค‘์ธ ์‹ ๊ณ  ์ˆ˜
  • resolved_reports: ์ฒ˜๋ฆฌ๋œ ์‹ ๊ณ  ์ˆ˜
  • dismissed_reports: ๊ธฐ๊ฐ๋œ ์‹ ๊ณ  ์ˆ˜
  • reports_by_category: ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์‹ ๊ณ  ์ˆ˜
  • reports_by_target_type: ๋Œ€์ƒ ํƒ€์ž…๋ณ„ ์‹ ๊ณ  ์ˆ˜

Error Responses:

401 Unauthorized - ์ธ์ฆ ์‹คํŒจ:

{
  "code": 401,
  "message": "๊ด€๋ฆฌ์ž ์ธ์ฆ ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.",
  "errors": []
}

500 Internal Server Error:

{
  "code": 500,
  "message": "์‹ ๊ณ  ํ†ต๊ณ„ ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "errors": []
}

15. ๊ด€๋ฆฌ์ž์šฉ ์‹ ๊ณ  ์ฒ˜๋ฆฌ (Process Report)

Endpoint: POST /admin/reports/:reportId/process

Description: ๊ด€๋ฆฌ์ž๊ฐ€ ํŠน์ • ์‹ ๊ณ ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์‹ ๊ณ  ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ณ  ๊ด€๋ฆฌ์ž ๋ฉ”๋ชจ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Path Parameters:

ํ•„๋“œ ํƒ€์ž… ํ•„์ˆ˜ ์„ค๋ช…
reportId number โœ… ์ฒ˜๋ฆฌํ•  ์‹ ๊ณ  ID

Request Headers:

Cookie: admin_access={accessToken}

Request Body:

{
  "status": "resolved",
  "admin_note": "์‚ฌ์šฉ์ž ์ฐจ๋‹จ ๋ฐ ๊ฒŒ์‹œ๋ฌผ ์‚ญ์ œ ์กฐ์น˜ ์™„๋ฃŒ"
}

Body Parameters:

ํ•„๋“œ ํƒ€์ž… ํ•„์ˆ˜ ์„ค๋ช…
status string โœ… ์ฒ˜๋ฆฌ ์ƒํƒœ (resolved, dismissed)
admin_note string โŒ ๊ด€๋ฆฌ์ž ๋ฉ”๋ชจ

๋™์ž‘ ๋ฐฉ์‹:

1. Admin ํ† ํฐ ๊ฒ€์ฆ (adminAuthMiddleware)
2. reportId ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฒ€์ฆ
3. ์‹ ๊ณ  ์กด์žฌ ํ™•์ธ ๋ฐ ์ƒํƒœ ๊ฒ€์ฆ (pending๋งŒ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ)
4. ํŠธ๋žœ์žญ์…˜ ์‹œ์ž‘
5. ์‹ ๊ณ  ์ƒํƒœ ์—…๋ฐ์ดํŠธ (status, admin_id, admin_note, resolved_at)
6. ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹

Response (200 OK):

{
  "code": 200,
  "message": "์‹ ๊ณ ๊ฐ€ ์ฒ˜๋ฆฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
  "data": null
}

Error Responses:

400 Bad Request - ์ž˜๋ชป๋œ reportId:

{
  "code": 400,
  "message": "์˜ฌ๋ฐ”๋ฅธ ์‹ ๊ณ  ID๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.",
  "errors": []
}

400 Bad Request - ์ž˜๋ชป๋œ ์ƒํƒœ ๊ฐ’:

{
  "code": 400,
  "message": "์˜ฌ๋ฐ”๋ฅธ ์ฒ˜๋ฆฌ ์ƒํƒœ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.",
  "errors": []
}

401 Unauthorized - ์ธ์ฆ ์‹คํŒจ:

{
  "code": 401,
  "message": "๊ด€๋ฆฌ์ž ์ธ์ฆ ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.",
  "errors": []
}

404 Not Found - ์‹ ๊ณ  ์—†์Œ:

{
  "code": 404,
  "message": "์‹ ๊ณ ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.",
  "errors": []
}

404 Not Found - ์ด๋ฏธ ์ฒ˜๋ฆฌ๋จ:

{
  "code": 404,
  "message": "์ด๋ฏธ ์ฒ˜๋ฆฌ๋œ ์‹ ๊ณ ์ž…๋‹ˆ๋‹ค.",
  "errors": []
}

500 Internal Server Error:

{
  "code": 500,
  "message": "์‹ ๊ณ  ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "errors": []
}

๐Ÿ” Admin ์ธ์ฆ ๋„๋ฉ”์ธ (Admin Authentication Domain)

๐ŸŒ OAuth ๋กœ๊ทธ์ธ ํ๋ฆ„

[Admin Web]
  โ†“ (๊ด€๋ฆฌ์ž ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ)
GET /admin/auth/kakao/start
  โ†“ (302 redirect + state ์ฟ ํ‚ค)
์นด์นด์˜ค ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€
  โ†“ (์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ)
์นด์นด์˜ค redirect
GET /admin/auth/kakao/callback?code=AUTH_CODE&state=STATE
  โ†“
[Server] CSRF ๊ฒ€์ฆ โ†’ ํ† ํฐ ๊ตํ™˜ โ†’ Admin ๊ฒ€์ฆ โ†’ ์ฟ ํ‚ค ์„ค์ •
  โ†“ (302 redirect)
ํ”„๋ก ํŠธ์—”๋“œ ๋Œ€์‹œ๋ณด๋“œ (${ADMIN_WEB_URL}/dashboard)

๐Ÿช ์ฟ ํ‚ค ์„ค์ • ๊ฐ•ํ™”

  • admin_access: HttpOnly; Secure; SameSite=Lax (15๋ถ„)
  • admin_refresh: HttpOnly; Secure; SameSite=Strict; Path=/admin/auth/refresh (15์ผ)
  • admin_oauth_state: HttpOnly; SameSite=Lax (10๋ถ„, ์ผํšŒ์šฉ)

1. ์นด์นด์˜ค OAuth ๋กœ๊ทธ์ธ ์‹œ์ž‘ (Start Kakao OAuth Login)

Endpoint: GET /admin/auth/kakao/start

Description: ์นด์นด์˜ค OAuth ๋กœ๊ทธ์ธ์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. CSRF ๋ฐฉ์ง€์šฉ state๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์นด์นด์˜ค ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•ฉ๋‹ˆ๋‹ค.

Request Headers: ์—†์Œ

Request Body: ์—†์Œ

๋™์ž‘ ๋ฐฉ์‹:

1. CSRF ๋ฐฉ์ง€์šฉ state ์ƒ์„ฑ (32๋ฐ”์ดํŠธ ๋žœ๋ค)
2. state๋ฅผ admin_oauth_state ์ฟ ํ‚ค์— ์ €์žฅ
3. ์นด์นด์˜ค OAuth ์ธ์ฆ URL ์ƒ์„ฑ (state ํฌํ•จ)
4. ์นด์นด์˜ค ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ 302 ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ

์นด์นด์˜ค OAuth URL ์˜ˆ์‹œ:

GET https://kauth.kakao.com/oauth/authorize?client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&response_type=code&state={STATE}

Response: 302 ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ (์นด์นด์˜ค ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€)

Error Responses:

500 Internal Server Error - ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋ˆ„๋ฝ:

{
  "code": 500,
  "message": "์นด์นด์˜ค ๋กœ๊ทธ์ธ ์‹œ์ž‘ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "errors": []
}

2. ์นด์นด์˜ค OAuth ์ฝœ๋ฐฑ ์ฒ˜๋ฆฌ (Handle Kakao OAuth Callback)

Endpoint: GET /admin/auth/kakao/callback

Description: ์นด์นด์˜ค OAuth ์ฝœ๋ฐฑ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. authorization code์™€ state๋ฅผ ๊ฒ€์ฆํ•˜๊ณ  Admin ์ธ์ฆ์„ ์™„๋ฃŒํ•ฉ๋‹ˆ๋‹ค.

Query Parameters:

ํ•„๋“œ ํƒ€์ž… ํ•„์ˆ˜ ์„ค๋ช…
code string โœ… ์นด์นด์˜ค authorization code
state string โœ… CSRF ๋ฐฉ์ง€์šฉ state ๊ฐ’
error string โŒ ์นด์นด์˜ค ์—๋Ÿฌ ์ฝ”๋“œ (์‹คํŒจ ์‹œ)

๋™์ž‘ ๋ฐฉ์‹:

1. authorization code์™€ state ๊ฒ€์ฆ
2. ์ €์žฅ๋œ state ์ฟ ํ‚ค์™€ ๋น„๊ต (CSRF ๋ฐฉ์ง€)
3. state ์ฟ ํ‚ค ์‚ญ์ œ (์ผํšŒ์šฉ)
4. code โ†’ ์นด์นด์˜ค access_token ๊ตํ™˜
5. ์นด์นด์˜ค ์‚ฌ์šฉ์ž ์ •๋ณด ์กฐํšŒ
6. kakao_user_id๋กœ Admin ํ…Œ์ด๋ธ” ๊ฒ€์ƒ‰
7. Admin์ด ์—†์œผ๋ฉด "๊ด€๋ฆฌ์ž ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค" ์—๋Ÿฌ
8. ๊ณ„์ • ์ž ๊ธˆ ์ƒํƒœ ํ™•์ธ
9. ๊ธฐ์กด RefreshToken ์‚ญ์ œ
10. JWT ํ† ํฐ ์ƒ์„ฑ ๋ฐ ํ•ด์‹œ ์ €์žฅ
11. ๋กœ๊ทธ์ธ ์‹œ๊ฐ ๋ฐ ์‹คํŒจ ์นด์šดํŠธ ์ดˆ๊ธฐํ™”
12. admin_access/admin_refresh ์ฟ ํ‚ค ์„ค์ •
13. ํ”„๋ก ํŠธ์—”๋“œ ๋Œ€์‹œ๋ณด๋“œ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ

์›น ์ฟ ํ‚ค ์„ค์ •:

  • admin_access: HttpOnly; Secure; SameSite=Lax (15๋ถ„)
  • admin_refresh: HttpOnly; Secure; SameSite=Strict; Path=/admin/auth/refresh (15์ผ)

Response: 302 ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ

์„ฑ๊ณต ์‹œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ: ${ADMIN_WEB_URL}/dashboard

์‹คํŒจ ์‹œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ:

  • ${ADMIN_WEB_URL}/login?error=not_admin - ๊ด€๋ฆฌ์ž ๊ถŒํ•œ ์—†์Œ
  • ${ADMIN_WEB_URL}/login?error=account_locked - ๊ณ„์ • ์ž ๊ธˆ
  • ${ADMIN_WEB_URL}/login?error=invalid_state - CSRF ๊ณต๊ฒฉ ๊ฐ์ง€
  • ${ADMIN_WEB_URL}/login?error=login_failed - ๊ธฐํƒ€ ๋กœ๊ทธ์ธ ์‹คํŒจ

Error Handling:

  • ์นด์นด์˜ค ์—๋Ÿฌ โ†’ kakao_oauth_failed
  • Authorization code ๋ˆ„๋ฝ โ†’ no_authorization_code
  • State ๋ถˆ์ผ์น˜ โ†’ invalid_state

3. ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ (Admin Token Refresh)

Endpoint: POST /admin/auth/refresh

Description: RefreshToken์„ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ๋กœ์šด AccessToken์„ ๋ฐœ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ํ•ด์‹œ๋œ ํ† ํฐ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.

Request Headers:

Cookie: admin_refresh={refreshToken}

Request Body: ์—†์Œ

๋™์ž‘ ๋ฐฉ์‹:

1. ์ฟ ํ‚ค์—์„œ admin_refresh ์ถ”์ถœ
2. RefreshToken ๊ฒ€์ฆ (admin_refresh ํƒ€์ž… ํ™•์ธ)
3. ํ† ํฐ ํ•ด์‹œ ์ƒ์„ฑ ๋ฐ DB ๋น„๊ต
4. ํ† ํฐ ๋งŒ๋ฃŒ/์ทจ์†Œ ์ƒํƒœ ํ™•์ธ
5. Admin ์กด์žฌ ๋ฐ ํ™œ์„ฑ ์ƒํƒœ ํ™•์ธ
6. ์ƒˆ AccessToken ์ƒ์„ฑ

Response (200 OK):

{
  "code": 200,
  "message": "ํ† ํฐ์ด ์žฌ๋ฐœ๊ธ‰๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }
}

Error Responses:

400 Bad Request - RefreshToken ๋ˆ„๋ฝ:

{
  "code": 400,
  "message": "๊ด€๋ฆฌ์ž RefreshToken์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.",
  "errors": []
}

401 Unauthorized - ํ† ํฐ ๋งŒ๋ฃŒ:

{
  "code": 401,
  "message": "๊ด€๋ฆฌ์ž RefreshToken์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
  "errors": [
    {
      "field": "refreshToken",
      "message": "TOKEN_EXPIRED"
    }
  ]
}

401 Unauthorized - ํ† ํฐ ์ทจ์†Œ๋จ:

{
  "code": 401,
  "message": "์œ ํšจํ•˜์ง€ ์•Š์€ ๊ด€๋ฆฌ์ž RefreshToken์ž…๋‹ˆ๋‹ค.",
  "errors": []
}

4. ๋กœ๊ทธ์•„์›ƒ (Admin Logout)

Endpoint: POST /admin/auth/logout

Description: ๊ด€๋ฆฌ์ž ๊ณ„์ •์—์„œ ๋กœ๊ทธ์•„์›ƒํ•˜๊ณ  ๋ชจ๋“  ํ† ํฐ์„ ๋ฌดํšจํ™”ํ•ฉ๋‹ˆ๋‹ค.

Request Headers:

Cookie: admin_access={accessToken}
Cookie: admin_refresh={refreshToken}

Request Body: ์—†์Œ

๋™์ž‘ ๋ฐฉ์‹:

1. AccessToken ๊ฒ€์ฆ ์‹œ๋„
2. RefreshToken ๊ฒ€์ฆ ์‹œ๋„ (fallback)
3. adminId ์ถ”์ถœ
4. DB์—์„œ ํ•ด๋‹น adminId์˜ ๋ชจ๋“  RefreshToken ์‚ญ์ œ
5. ์ฟ ํ‚ค ์‚ญ์ œ ์ง€์‹œ

Response (200 OK):

{
  "code": 200,
  "message": "๊ด€๋ฆฌ์ž ๋กœ๊ทธ์•„์›ƒ์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
  "data": null
}

์›น ์ฟ ํ‚ค ์‚ญ์ œ:

  • admin_access ์ฟ ํ‚ค ์‚ญ์ œ
  • admin_refresh ์ฟ ํ‚ค ์‚ญ์ œ

ํŠน๋ณ„ ์ผ€์ด์Šค - ํ† ํฐ ์—†์Œ:

{
  "code": 200,
  "message": "๋กœ๊ทธ์•„์›ƒ์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. (ํด๋ผ์ด์–ธํŠธ ์ •๋ฆฌ๋งŒ ์ง„ํ–‰๋จ)",
  "data": null
}

5. ๊ด€๋ฆฌ์ž ์ •๋ณด ์กฐํšŒ (Get Admin Info)

Endpoint: GET /admin/auth/me

Description: ํ˜„์žฌ ๋กœ๊ทธ์ธ๋œ ๊ด€๋ฆฌ์ž์˜ ์ •๋ณด๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.

Request Headers:

Cookie: admin_access={accessToken}

Request Body: ์—†์Œ

Response (200 OK):

{
  "code": 200,
  "message": "๊ด€๋ฆฌ์ž ์ •๋ณด ์กฐํšŒ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
  "data": {
    "id": 1,
    "email": "admin@kakao.com",
    "name": "๊ด€๋ฆฌ์ž",
    "role": "OWNER",
    "last_login_at": "2025-12-29T12:30:00.000Z",
    "created_at": "2025-01-01T00:00:00.000Z"
  }
}

Error Responses:

401 Unauthorized - ์ธ์ฆ ์‹คํŒจ:

{
  "code": 401,
  "message": "๊ด€๋ฆฌ์ž ์ธ์ฆ ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.",
  "errors": []
}

404 Not Found - ๊ด€๋ฆฌ์ž ์—†์Œ:

{
  "code": 404,
  "message": "๊ด€๋ฆฌ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.",
  "errors": []
}

๐Ÿ”‘ JWT ํ† ํฐ ์ •๋ณด (Admin ์ „์šฉ)

Admin AccessToken

  • ์œ ํšจ๊ธฐ๊ฐ„: 15๋ถ„
  • ์šฉ๋„: Admin API ์š”์ฒญ ์ธ์ฆ
  • ํฌํ•จ ์ •๋ณด: adminId, type: 'admin_access'

Admin RefreshToken

  • ์œ ํšจ๊ธฐ๊ฐ„: 15์ผ
  • ์šฉ๋„: AccessToken ์žฌ๋ฐœ๊ธ‰
  • ์ €์žฅ ๋ฐฉ์‹: SHA-256 ํ•ด์‹œ ์ €์žฅ (๋ณด์•ˆ ๊ฐ•ํ™”)
  • ํฌํ•จ ์ •๋ณด: adminId, type: 'admin_refresh'

๐Ÿ“ ์‚ฌ์šฉ ์˜ˆ์‹œ

์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ Admin ๋กœ๊ทธ์ธ ํ๋ฆ„

  1. ์นด์นด์˜ค ๋กœ๊ทธ์ธ ์‹œ์ž‘
# ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง์ ‘ ์ ‘๊ทผ
GET http://localhost:3001/admin/auth/kakao/start
# โ†’ ์นด์นด์˜ค ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ (state ์ฟ ํ‚ค ์ž๋™ ์„ค์ •)
  1. ์นด์นด์˜ค ๋กœ๊ทธ์ธ ์™„๋ฃŒ
์‚ฌ์šฉ์ž๊ฐ€ ์นด์นด์˜ค ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธ
โ†’ ์นด์นด์˜ค๊ฐ€ ์ฝœ๋ฐฑ URL๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ (?code=xxx&state=yyy)
โ†’ ์„œ๋ฒ„์—์„œ CSRF ๊ฒ€์ฆ ํ›„ Admin ์ธ์ฆ
โ†’ admin_access/admin_refresh ์ฟ ํ‚ค ์„ค์ •
โ†’ ํ”„๋ก ํŠธ์—”๋“œ ๋Œ€์‹œ๋ณด๋“œ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
  1. ๊ด€๋ฆฌ์ž ์ •๋ณด ์กฐํšŒ
curl -X GET http://localhost:3001/admin/auth/me \
  --cookie "admin_access=token_here"
  1. ํ† ํฐ ์žฌ๋ฐœ๊ธ‰
curl -X POST http://localhost:3001/admin/auth/refresh \
  --cookie "admin_refresh=token_here"
  1. ๋กœ๊ทธ์•„์›ƒ
curl -X POST http://localhost:3001/admin/auth/logout \
  --cookie "admin_access=token_here"

๐Ÿ›ก๏ธ ์ธ์ฆ ๋ฏธ๋“ค์›จ์–ด ์ •๋ณด

Admin ์ „์šฉ ๋ฏธ๋“ค์›จ์–ด

1. adminAuthMiddleware

  • ์šฉ๋„: Admin API ์ธ์ฆ (AccessToken ๊ฒ€์ฆ)
  • ํ† ํฐ ์œ„์น˜: ์ฟ ํ‚ค์˜ admin_access
  • ํƒ€์ž… ํ™•์ธ: admin_access ํƒ€์ž… ๊ฒ€์ฆ

2. adminRefreshTokenMiddleware

  • ์šฉ๋„: ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ์ „์šฉ (RefreshToken ํ•ด์‹œ ๊ฒ€์ฆ)
  • ํ† ํฐ ์œ„์น˜: ์ฟ ํ‚ค์˜ admin_refresh
  • ํ•ด์‹œ ๊ฒ€์ฆ: DB์—์„œ SHA-256 ํ•ด์‹œ ๋น„๊ต
  • ํƒ€์ž… ํ™•์ธ: admin_refresh ํƒ€์ž… ๊ฒ€์ฆ

3. adminLogoutMiddleware

  • ์šฉ๋„: ํ–ฅ์ƒ๋œ ๋กœ๊ทธ์•„์›ƒ (AccessToken + RefreshToken ํ™œ์šฉ)
  • ํŠน์ง•: ํ† ํฐ ๋งŒ๋ฃŒ๋˜์–ด๋„ adminId ์ถ”์ถœ ๊ฐ€๋Šฅ
  • fallback: ๋‘˜ ๋‹ค ์‹คํŒจ์‹œ graceful degradation

4. adminRoleMiddleware (ํ–ฅํ›„ ํ™•์žฅ)

// ์—ญํ•  ๊ธฐ๋ฐ˜ ๊ถŒํ•œ ์ œ์–ด
app.get('/admin/users',
  adminAuthMiddleware,
  adminRoleMiddleware(['ADMIN', 'OWNER']),
  getUsersHandler
);

๋ฏธ๋“ค์›จ์–ด ์ ์šฉ ํ˜„ํ™ฉ

API ๋ฏธ๋“ค์›จ์–ด ์„ค๋ช…
/admin/auth/kakao/start ์—†์Œ ์ธ์ฆ ๋ถˆํ•„์š” (OAuth ์‹œ์ž‘)
/admin/auth/kakao/callback ์—†์Œ ์ธ์ฆ ๋ถˆํ•„์š” (OAuth ์ฝœ๋ฐฑ)
/admin/auth/refresh adminRefreshTokenMiddleware RefreshToken ํ•ด์‹œ ๊ฒ€์ฆ
/admin/auth/logout adminLogoutMiddleware ํ–ฅ์ƒ๋œ ๋กœ๊ทธ์•„์›ƒ
/admin/auth/me adminAuthMiddleware AccessToken ๊ฒ€์ฆ
/admin/users adminAuthMiddleware AccessToken ๊ฒ€์ฆ
/admin/inquiries adminAuthMiddleware AccessToken ๊ฒ€์ฆ
/admin/posts adminAuthMiddleware AccessToken ๊ฒ€์ฆ
/admin/posts/:postId adminAuthMiddleware AccessToken ๊ฒ€์ฆ
/admin/feeds adminAuthMiddleware AccessToken ๊ฒ€์ฆ
/admin/feeds/:feedId adminAuthMiddleware AccessToken ๊ฒ€์ฆ

๐Ÿšจ ์—๋Ÿฌ ์ฝ”๋“œ ์ •๋ฆฌ

HTTP ์ƒํƒœ ์ฝ”๋“œ ์„ค๋ช… ์‚ฌ์šฉ ์˜ˆ์‹œ
200 OK ์„ฑ๊ณต์ ์ธ ์š”์ฒญ ์ฒ˜๋ฆฌ
400 Bad Request ์ž˜๋ชป๋œ ์š”์ฒญ (์ž…๋ ฅ๊ฐ’ ์˜ค๋ฅ˜)
401 Unauthorized ์ธ์ฆ ์‹คํŒจ (ํ† ํฐ ๋ฌดํšจ)
403 Forbidden ๊ถŒํ•œ ์—†์Œ (๋ฏธ๋“ฑ๋ก Admin)
404 Not Found ๋ฆฌ์†Œ์Šค ์—†์Œ (Admin ๊ณ„์ • ์—†์Œ)
423 Locked ๊ณ„์ • ์ž ๊ธˆ (์‹คํŒจ ํšŸ์ˆ˜ ์ดˆ๊ณผ)
500 Internal Server Error ์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜

๐Ÿ” ํ† ํฐ ์—๋Ÿฌ ์ƒ์„ธ ๊ตฌ๋ถ„

Admin AccessToken ์—๋Ÿฌ

ํ† ํฐ ๋งŒ๋ฃŒ:

{
  "code": 401,
  "message": "๊ด€๋ฆฌ์ž ์ธ์ฆ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.",
  "errors": [
    {
      "field": "token",
      "message": "TOKEN_EXPIRED"
    }
  ]
}

์ž˜๋ชป๋œ ํ† ํฐ ํƒ€์ž…:

{
  "code": 401,
  "message": "์œ ํšจํ•˜์ง€ ์•Š์€ ๊ด€๋ฆฌ์ž ํ† ํฐ์ž…๋‹ˆ๋‹ค.",
  "errors": []
}

Admin RefreshToken ์—๋Ÿฌ

ํ† ํฐ ๋งŒ๋ฃŒ:

{
  "code": 401,
  "message": "๊ด€๋ฆฌ์ž RefreshToken์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
  "errors": [
    {
      "field": "refreshToken",
      "message": "TOKEN_EXPIRED"
    }
  ]
}

ํ•ด์‹œ ๋ถˆ์ผ์น˜ (ํ† ํฐ ๋ณ€์กฐ):

{
  "code": 401,
  "message": "์œ ํšจํ•˜์ง€ ์•Š์€ ๊ด€๋ฆฌ์ž RefreshToken์ž…๋‹ˆ๋‹ค.",
  "errors": []
}

๐Ÿ” ๋ณด์•ˆ ๊ฐ•ํ™” ํŠน์ง•

Admin ์ „์šฉ ๋ณด์•ˆ

  • ํ•ด์‹œ ์ €์žฅ: RefreshToken์„ SHA-256์œผ๋กœ ํ•ด์‹œํ•˜์—ฌ ์ €์žฅ
  • ๋“ฑ๋ก ํ•„ํ„ฐ๋ง: ๋ฏธ๋“ฑ๋ก ์นด์นด์˜ค ๊ณ„์ • ์ ‘๊ทผ ๊ฑฐ๋ถ€
  • ์‹คํŒจ ์ œํ•œ: ๋กœ๊ทธ์ธ ์‹คํŒจ ํšŸ์ˆ˜ ์ œํ•œ ๋ฐ ๊ณ„์ • ์ž ๊ธˆ
  • ํ† ํฐ ๋กœํ…Œ์ด์…˜: ๋กœ๊ทธ์ธ ์‹œ ๊ธฐ์กด ํ† ํฐ ์ž๋™ ๋ฌดํšจํ™”

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ตฌ์กฐ

  • Admin ํ…Œ์ด๋ธ”: ๊ด€๋ฆฌ์ž ๊ธฐ๋ณธ ์ •๋ณด
  • AdminRefreshTokens ํ…Œ์ด๋ธ”: ํ•ด์‹œ๋œ ํ† ํฐ ๋ฐ ๋กœํ…Œ์ด์…˜ ์ถ”์ 
  • ์™ธ๋ž˜ ํ‚ค ์ œ์•ฝ: CASCADE ์‚ญ์ œ ์ง€์›

์„ธ์…˜ ๊ด€๋ฆฌ

  • ๋‹จ์ผ ์„ธ์…˜: ๋กœ๊ทธ์ธ ์‹œ ๊ธฐ์กด RefreshToken ์ž๋™ ์‚ญ์ œ
  • ํ† ํฐ ๋งŒ๋ฃŒ: AccessToken 15๋ถ„, RefreshToken 15์ผ
  • ๊ฐ•์ œ ๋กœ๊ทธ์•„์›ƒ: ๋ณด์•ˆ ์œ„ํ˜‘ ์‹œ ์ˆ˜๋™ ํ† ํฐ ๋ฌดํšจํ™” ๊ฐ€๋Šฅ

๐Ÿ“š ์ถ”๊ฐ€ ์ •๋ณด

  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค: MySQL (maple_talk_sns)
  • ORM: Sequelize
  • ์ธ์ฆ ๋ฐฉ์‹: JWT (JSON Web Token) + ํ•ด์‹œ
  • ์นด์นด์˜ค API: ์‚ฌ์šฉ์ž ์ •๋ณด ์กฐํšŒ ๋ฐ ๊ฒ€์ฆ
  • ํ”Œ๋žซํผ: ์›น ๋ธŒ๋ผ์šฐ์ € ์ „์šฉ
  • CORS: ์›น ๋„๋ฉ”์ธ ํ—ˆ์šฉ (localhost:3000 ๋“ฑ)

๐Ÿ—„๏ธ Admin ๊ณ„์ • ๋“ฑ๋ก ๋ฐฉ๋ฒ•

DB ์ง์ ‘ ๋“ฑ๋ก (๊ถŒ์žฅ)

INSERT INTO Admins (
  kakao_user_id,
  email,
  name,
  role,
  is_active
) VALUES (
  123456789,  -- ์นด์นด์˜ค ์‚ฌ์šฉ์ž ID
  'admin@kakao.com',
  '๊ด€๋ฆฌ์ž',
  'OWNER',
  1
);

๊ด€๋ฆฌ์ž ๋“ฑ๋ก API (ํ–ฅํ›„ ๊ตฌํ˜„)

// OWNER ๊ถŒํ•œ ํ•„์š”
POST /admin/register
{
  "kakao_user_id": 123456789,
  "email": "admin@kakao.com",
  "name": "๊ด€๋ฆฌ์ž",
  "role": "ADMIN"
}

๐Ÿš€ ์‹œ์ž‘ํ•˜๊ธฐ

1. ์นด์นด์˜ค ๊ฐœ๋ฐœ์ž์„ผํ„ฐ ์„ค์ •

  1. ์นด์นด์˜ค ๊ฐœ๋ฐœ์ž์„ผํ„ฐ ์ ‘์†
  2. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ถ”๊ฐ€ โ†’ ์›น ํ”Œ๋žซํผ ์„ ํƒ
  3. ์•ฑ ํ‚ค โ†’ REST API ํ‚ค ๋ณต์‚ฌ (CLIENT_ID)
  4. ์•ฑ ํ‚ค โ†’ Client Secret ๋ณต์‚ฌ (CLIENT_SECRET)
  5. Redirect URI ์„ค์ •: http://localhost:3001/admin/auth/kakao/callback

2. ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ค์ •

# .env ํŒŒ์ผ์— ์ถ”๊ฐ€
KAKAO_CLIENT_ID=์นด์นด์˜ค_REST_API_ํ‚ค          # ํ•„์ˆ˜: ์นด์นด์˜ค ์ฝ˜์†” REST API ํ‚ค
KAKAO_CLIENT_SECRET=์นด์นด์˜ค_Client_Secret    # ์„ ํƒ: Client Secret (์„ค์ •๋œ ๊ฒฝ์šฐ๋งŒ)
KAKAO_REDIRECT_URI=http://localhost:3001/admin/auth/kakao/callback  # ํ•„์ˆ˜: ์ฝœ๋ฐฑ URL
ADMIN_WEB_URL=http://localhost:5173         # ํ•„์ˆ˜: ํ”„๋ก ํŠธ์—”๋“œ URL (React/Vite)

ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ค๋ช…

  • KAKAO_CLIENT_ID: ์นด์นด์˜ค ๊ฐœ๋ฐœ์ž์„ผํ„ฐ โ†’ ์•ฑ ์„ค์ • โ†’ ์•ฑ ํ‚ค โ†’ REST API ํ‚ค ๊ฐ’
  • KAKAO_CLIENT_SECRET: ์นด์นด์˜ค ๊ฐœ๋ฐœ์ž์„ผํ„ฐ โ†’ ์•ฑ ์„ค์ • โ†’ ์•ฑ ํ‚ค โ†’ Client Secret ๊ฐ’ (์„ ํƒ์‚ฌํ•ญ)
  • KAKAO_REDIRECT_URI: ์นด์นด์˜ค ๊ฐœ๋ฐœ์ž์„ผํ„ฐ์— ๋“ฑ๋ก๋œ ์ฝœ๋ฐฑ URL๊ณผ ์™„์ „ํžˆ ๋™์ผํ•ด์•ผ ํ•จ
  • ADMIN_WEB_URL: React/Vite ํ”„๋ก ํŠธ์—”๋“œ URL (๋กœ๊ทธ์ธ ์„ฑ๊ณต/์‹คํŒจ ์‹œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ)

3. Admin ๊ณ„์ • ๋“ฑ๋ก

INSERT INTO Admins (kakao_user_id, email, name, role, is_active)
VALUES (์นด์นด์˜ค_์‚ฌ์šฉ์ž_ID, 'admin@kakao.com', '๊ด€๋ฆฌ์ž', 'OWNER', 1);

4. ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ ๋ฐ ํ…Œ์ŠคํŠธ

# ์„œ๋ฒ„ ์žฌ์‹œ์ž‘
npm run dev

# ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ…Œ์ŠคํŠธ
http://localhost:3001/admin/auth/kakao/start

5. ํ”„๋ก ํŠธ์—”๋“œ ์—ฐ๋™

// React์—์„œ ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ
const handleLogin = () => {
  window.location.href = '/admin/auth/kakao/start';
};

// ๋กœ๊ทธ์•„์›ƒ
const handleLogout = async () => {
  await fetch('/admin/auth/logout', { method: 'POST' });
  // ์ฟ ํ‚ค ์ž๋™ ์‚ญ์ œ๋จ
};

์„ฑ๋Šฅ ์ตœ์ ํ™”

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ธ๋ฑ์Šค

  • Admins.kakao_user_id (Unique)
  • Admins.is_active + Admins.kakao_user_id
  • AdminRefreshTokens.token_hash (Unique)
  • AdminRefreshTokens.admin_id + AdminRefreshTokens.revoked_at

์บ์‹ฑ ์ „๋žต

  • Admin ๊ธฐ๋ณธ ์ •๋ณด: 10๋ถ„ ์บ์‹ฑ
  • ํ† ํฐ ๊ฒ€์ฆ ๊ฒฐ๊ณผ: 5๋ถ„ ์บ์‹ฑ
  • ์—ญํ•  ๊ถŒํ•œ ์ •๋ณด: ์˜๊ตฌ ์บ์‹ฑ

๋ณด์•ˆ ์ตœ์ ํ™”

  • ํ† ํฐ ํ•ด์‹œ ๊ฒ€์ฆ (DB ๋ถ€ํ•˜ ๊ฐ์†Œ)
  • ๋กœ๊ทธ์ธ ์‹คํŒจ ํšŸ์ˆ˜ ๋ฉ”๋ชจ๋ฆฌ ์บ์‹ฑ
  • IP ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œํ•œ (ํ–ฅํ›„ ๊ตฌํ˜„)

์ด ๋ฌธ์„œ๋Š” Maple Talk SNS Admin API์˜ ์™„์ „ํ•œ ๋ช…์„ธ์„œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋ณด์•ˆ๊ณผ ์•ˆ์ •์„ฑ์„ ์ตœ์šฐ์„ ์œผ๋กœ ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.