> ## Documentation Index
> Fetch the complete documentation index at: https://api-docs.vpms.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 게시글 및 알림 (Article & Notification)

> 게시글 관리 및 사용자 알림 API

## 개요

Article 도메인은 공지사항 및 게시글 관리, 사용자 알림 시스템을 제공합니다. 파일 첨부 기능과 페이지네이션을 지원합니다.

## Types

### Article

게시글 정보를 나타냅니다.

<ResponseField name="id" type="ID!">
  게시글 ID
</ResponseField>

<ResponseField name="subject" type="String">
  게시글 제목
</ResponseField>

<ResponseField name="content" type="String">
  게시글 내용
</ResponseField>

<ResponseField name="attachments" type="[String]">
  첨부 파일 URL 목록
</ResponseField>

<ResponseField name="type" type="String">
  게시글 타입 (예: "notice", "announcement")
</ResponseField>

<ResponseField name="tag" type="String">
  게시글 태그
</ResponseField>

<ResponseField name="createdAt" type="Date!">
  생성 일시
</ResponseField>

***

### UserNotification

사용자 알림 정보를 나타냅니다.

<ResponseField name="id" type="ID!">
  알림 ID
</ResponseField>

<ResponseField name="key" type="String!">
  알림 키 (고유 식별자)
</ResponseField>

<ResponseField name="summary" type="String!">
  알림 요약
</ResponseField>

<ResponseField name="description" type="String">
  알림 상세 내용
</ResponseField>

<ResponseField name="type" type="String">
  알림 타입
</ResponseField>

<ResponseField name="status" type="Int">
  알림 상태 코드
</ResponseField>

<ResponseField name="isRead" type="Boolean">
  읽음 여부
</ResponseField>

<ResponseField name="isPublic" type="Boolean">
  공개 알림 여부
</ResponseField>

<ResponseField name="userId" type="ID">
  대상 사용자 ID
</ResponseField>

<ResponseField name="accommodationId" type="ID">
  관련 숙박 시설 ID
</ResponseField>

<ResponseField name="data" type="String">
  추가 데이터 (JSON 형식)
</ResponseField>

<ResponseField name="createdAt" type="Date!">
  생성 일시
</ResponseField>

<ResponseField name="expiresAt" type="Date">
  만료 일시
</ResponseField>

<ResponseField name="url" type="String">
  관련 URL
</ResponseField>

***

## Queries

### getArticles

게시글 목록을 페이지네이션으로 조회합니다.

#### GraphQL Signature

```graphql theme={null}
query GetArticles($first: Int, $after: String) {
  getArticles(first: $first, after: $after) {
    edges {
      cursor
      node {
        id
        subject
        content
        attachments
        type
        tag
        createdAt
      }
    }
    pageInfo {
      hasNextPage
      hasPreviousPage
      startCursor
      endCursor
    }
    totalCount
  }
}
```

#### 파라미터

<ParamField path="first" type="Int">
  가져올 게시글 개수
</ParamField>

<ParamField path="after" type="String">
  페이지네이션 커서 (이 커서 이후의 게시글)
</ParamField>

#### 응답

<ResponseField name="edges" type="[ArticleEdge!]!">
  게시글 목록
</ResponseField>

<ResponseField name="pageInfo" type="PageInfo!">
  페이지 정보
</ResponseField>

<ResponseField name="totalCount" type="Int!">
  전체 게시글 수
</ResponseField>

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  query {
    getArticles(first: 10) {
      edges {
        node {
          id
          subject
          content
          type
          createdAt
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
      totalCount
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "getArticles": {
        "edges": [
          {
            "node": {
              "id": "01HQKS9V8X2N3P4Q5R6S7T8U9V",
              "subject": "시스템 점검 안내",
              "content": "2025년 1월 15일 시스템 점검이 있습니다.",
              "type": "notice",
              "createdAt": "2025-01-10T09:00:00Z"
            }
          }
        ],
        "pageInfo": {
          "hasNextPage": true,
          "endCursor": "Y3Vyc29yOnYyOpK5..."
        },
        "totalCount": 42
      }
    }
  }
  ```
</CodeGroup>

***

### getArticle

특정 게시글의 상세 정보를 조회합니다.

#### GraphQL Signature

```graphql theme={null}
query GetArticle($id: ID!) {
  getArticle(id: $id) {
    id
    subject
    content
    attachments
    type
    tag
    createdAt
  }
}
```

#### 파라미터

<ParamField path="id" type="ID!" required>
  게시글 ID
</ParamField>

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  query {
    getArticle(id: "01HQKS9V8X2N3P4Q5R6S7T8U9V") {
      id
      subject
      content
      attachments
      type
      createdAt
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "getArticle": {
        "id": "01HQKS9V8X2N3P4Q5R6S7T8U9V",
        "subject": "시스템 점검 안내",
        "content": "2025년 1월 15일 02:00-04:00 시스템 점검이 있습니다.",
        "attachments": [
          "https://cdn.vpms.io/files/notice_123.pdf"
        ],
        "type": "notice",
        "createdAt": "2025-01-10T09:00:00Z"
      }
    }
  }
  ```
</CodeGroup>

***

### getMyUserNotifications

현재 로그인한 사용자의 알림 목록을 조회합니다.

#### GraphQL Signature

```graphql theme={null}
query GetMyUserNotifications(
  $first: Int
  $after: ID
  $accommodationId: ID
) {
  getMyUserNotifications(
    first: $first
    after: $after
    accommodationId: $accommodationId
  ) {
    edges {
      cursor
      node {
        id
        key
        summary
        description
        type
        status
        isRead
        isPublic
        userId
        accommodationId
        data
        createdAt
        expiresAt
        url
      }
    }
    pageInfo {
      hasNextPage
      hasPreviousPage
      startCursor
      endCursor
    }
    totalCount
  }
}
```

#### 파라미터

<ParamField path="first" type="Int">
  가져올 알림 개수
</ParamField>

<ParamField path="after" type="ID">
  이 ID 이후의 알림
</ParamField>

<ParamField path="accommodationId" type="ID">
  특정 숙박 시설 관련 알림만 필터링
</ParamField>

#### 응답

<ResponseField name="edges" type="[UserNotificationEdge!]!">
  알림 목록
</ResponseField>

<ResponseField name="pageInfo" type="PageInfo!">
  페이지 정보
</ResponseField>

<ResponseField name="totalCount" type="Int!">
  전체 알림 수
</ResponseField>

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  query {
    getMyUserNotifications(first: 20) {
      edges {
        node {
          id
          key
          summary
          description
          type
          isRead
          createdAt
          url
        }
      }
      totalCount
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "getMyUserNotifications": {
        "edges": [
          {
            "node": {
              "id": "01HQKS9V8X2N3P4Q5R6S7T8U9V",
              "key": "reservation_confirmed",
              "summary": "예약이 확정되었습니다",
              "description": "2025-01-20 체크인 예약이 확정되었습니다.",
              "type": "reservation",
              "isRead": false,
              "createdAt": "2025-01-10T15:30:00Z",
              "url": "/reservations/01HQKS9V8X"
            }
          }
        ],
        "totalCount": 5
      }
    }
  }
  ```
</CodeGroup>

<Info>
  이 API는 인증이 필요하며, 로그인한 사용자의 알림만 조회됩니다.
</Info>

***

## Mutations

### createArticle

새로운 게시글을 생성합니다.

#### GraphQL Signature

```graphql theme={null}
mutation CreateArticle($input: CreateArticleInput!) {
  createArticle(input: $input) {
    id
    subject
    content
    type
    tag
    createdAt
  }
}
```

#### 파라미터

<ParamField path="input.subject" type="String">
  게시글 제목
</ParamField>

<ParamField path="input.content" type="String">
  게시글 내용
</ParamField>

<ParamField path="input.attachments" type="String">
  첨부 파일 URL (JSON 배열 문자열)
</ParamField>

<ParamField path="input.type" type="String">
  게시글 타입
</ParamField>

<ParamField path="input.tag" type="String">
  게시글 태그
</ParamField>

#### 응답

<ResponseField name="Article" type="Article!">
  생성된 게시글 정보
</ResponseField>

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  mutation {
    createArticle(input: {
      subject: "신규 기능 안내"
      content: "새로운 기능이 추가되었습니다."
      type: "announcement"
      tag: "feature"
    }) {
      id
      subject
      createdAt
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "createArticle": {
        "id": "01HQKS9V8X2N3P4Q5R6S7T8U9V",
        "subject": "신규 기능 안내",
        "createdAt": "2025-01-10T10:00:00Z"
      }
    }
  }
  ```
</CodeGroup>

***

### updateArticle

기존 게시글을 수정합니다.

#### GraphQL Signature

```graphql theme={null}
mutation UpdateArticle($input: UpdateArticleInput!) {
  updateArticle(input: $input) {
    id
    subject
    content
    type
    tag
  }
}
```

#### 파라미터

<ParamField path="input.id" type="ID!" required>
  수정할 게시글 ID
</ParamField>

<ParamField path="input.subject" type="String">
  새로운 제목
</ParamField>

<ParamField path="input.content" type="String">
  새로운 내용
</ParamField>

<ParamField path="input.attachments" type="String">
  새로운 첨부 파일 URL
</ParamField>

<ParamField path="input.type" type="String">
  새로운 타입
</ParamField>

<ParamField path="input.tag" type="String">
  새로운 태그
</ParamField>

#### 응답

<ResponseField name="Article" type="Article!">
  수정된 게시글 정보
</ResponseField>

***

### deleteArticle

게시글을 삭제합니다.

#### GraphQL Signature

```graphql theme={null}
mutation DeleteArticle($id: ID!) {
  deleteArticle(id: $id)
}
```

#### 파라미터

<ParamField path="id" type="ID!" required>
  삭제할 게시글 ID
</ParamField>

#### 응답

<ResponseField name="result" type="Boolean!">
  삭제 성공 여부
</ResponseField>

***

### uploadArticleAttachments

게시글에 파일을 첨부합니다.

#### GraphQL Signature

```graphql theme={null}
mutation UploadArticleAttachments(
  $articleId: ID!
  $files: [Upload!]!
  $persistFiles: [String!]
) {
  uploadArticleAttachments(
    articleId: $articleId
    files: $files
    persistFiles: $persistFiles
  ) {
    id
    attachments
  }
}
```

#### 파라미터

<ParamField path="articleId" type="ID!" required>
  파일을 첨부할 게시글 ID
</ParamField>

<ParamField path="files" type="[Upload!]!" required>
  업로드할 파일 목록
</ParamField>

<ParamField path="persistFiles" type="[String!]">
  유지할 기존 파일 URL 목록
</ParamField>

#### 응답

<ResponseField name="Article" type="Article!">
  첨부 파일이 업데이트된 게시글 정보
</ResponseField>

<Info>
  `persistFiles`를 지정하지 않으면 기존 첨부 파일이 모두 제거됩니다.
</Info>

***

### publishUserNotification

사용자 알림을 발송합니다.

#### GraphQL Signature

```graphql theme={null}
mutation PublishUserNotification($input: UserNotificationInput!) {
  publishUserNotification(input: $input) {
    id
    key
    summary
    description
    type
    userId
    accommodationId
  }
}
```

#### 파라미터

<ParamField path="input.key" type="String!" required>
  알림 고유 키
</ParamField>

<ParamField path="input.summary" type="String!" required>
  알림 요약
</ParamField>

<ParamField path="input.description" type="String">
  알림 상세 내용
</ParamField>

<ParamField path="input.type" type="String!" required>
  알림 타입 (예: "reservation", "payment", "system")
</ParamField>

<ParamField path="input.status" type="Int">
  알림 상태 코드
</ParamField>

<ParamField path="input.isRead" type="Boolean">
  읽음 여부 (기본값: false)
</ParamField>

<ParamField path="input.isPublic" type="Boolean">
  공개 알림 여부
</ParamField>

<ParamField path="input.userId" type="ID">
  특정 사용자에게만 발송 (미지정 시 전체 공지)
</ParamField>

<ParamField path="input.accommodationId" type="ID">
  관련 숙박 시설 ID
</ParamField>

<ParamField path="input.accommodationAuthorities" type="[String!]">
  특정 권한을 가진 직원에게만 발송
</ParamField>

<ParamField path="input.expiresAt" type="Date">
  알림 만료 일시
</ParamField>

<ParamField path="input.data" type="String">
  추가 데이터 (JSON 문자열)
</ParamField>

<ParamField path="input.url" type="String">
  관련 URL
</ParamField>

#### 응답

<ResponseField name="notifications" type="[UserNotification]!">
  발송된 알림 목록 (여러 사용자에게 발송된 경우 배열로 반환)
</ResponseField>

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  mutation {
    publishUserNotification(input: {
      key: "reservation_confirmed"
      summary: "예약이 확정되었습니다"
      description: "2025-01-20 체크인 예약이 확정되었습니다."
      type: "reservation"
      userId: "01HQKS9V8X2N3P4Q5R6S7T8U9V"
      url: "/reservations/01HQKS9V8X"
      data: "{\"reservationId\": \"01HQKS9V8X\"}"
    }) {
      id
      key
      summary
      userId
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "publishUserNotification": [
        {
          "id": "01HQKS9V8Y3N4P5Q6R7S8T9U0W",
          "key": "reservation_confirmed",
          "summary": "예약이 확정되었습니다",
          "userId": "01HQKS9V8X2N3P4Q5R6S7T8U9V"
        }
      ]
    }
  }
  ```
</CodeGroup>

***

### deleteUserNotification

사용자 알림을 삭제합니다.

#### GraphQL Signature

```graphql theme={null}
mutation DeleteUserNotification($id: ID!) {
  deleteUserNotification(id: $id)
}
```

#### 파라미터

<ParamField path="id" type="ID!" required>
  삭제할 알림 ID
</ParamField>

#### 응답

<ResponseField name="result" type="Boolean!">
  삭제 성공 여부
</ResponseField>

***

### markReadUserNotifications

여러 알림을 읽음 처리합니다.

#### GraphQL Signature

```graphql theme={null}
mutation MarkReadUserNotifications($ids: [ID!]!) {
  markReadUserNotifications(ids: $ids)
}
```

#### 파라미터

<ParamField path="ids" type="[ID!]!" required>
  읽음 처리할 알림 ID 목록
</ParamField>

#### 응답

<ResponseField name="count" type="Int!">
  읽음 처리된 알림 개수
</ResponseField>

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  mutation {
    markReadUserNotifications(ids: [
      "01HQKS9V8X2N3P4Q5R6S7T8U9V",
      "01HQKS9V8Y3N4P5Q6R7S8T9U0W"
    ])
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "markReadUserNotifications": 2
    }
  }
  ```
</CodeGroup>

***

## 사용 흐름

### 게시글 관리 흐름

1. **게시글 작성**: `createArticle`로 게시글 생성
2. **파일 첨부** (선택): `uploadArticleAttachments`로 파일 업로드
3. **게시글 수정** (선택): `updateArticle`로 내용 수정
4. **게시글 삭제**: `deleteArticle`로 삭제

### 알림 시스템 흐름

1. **알림 발송**: `publishUserNotification`으로 알림 생성 및 발송
2. **알림 조회**: 사용자가 `getMyUserNotifications`로 알림 확인
3. **읽음 처리**: `markReadUserNotifications`로 알림 읽음 처리
4. **알림 삭제**: `deleteUserNotification`으로 알림 삭제

### 알림 타겟팅

**특정 사용자에게 발송**

```graphql theme={null}
publishUserNotification(input: {
  userId: "01HQKS9V8X"
  # ...
})
```

**특정 숙박 시설의 특정 권한 직원에게 발송**

```graphql theme={null}
publishUserNotification(input: {
  accommodationId: "01HQKS9V8X"
  accommodationAuthorities: ["manager", "admin"]
  # ...
})
```

**전체 공지**

```graphql theme={null}
publishUserNotification(input: {
  isPublic: true
  # ...
})
```

## 관련 API

* [사용자 API](/api-reference/user-svc/user) - 사용자 정보 관리
* [Core Service](/api-reference/core-svc/introduction) - 예약 관련 알림
