> ## 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.

# 알림 (Notification)

> 실시간 알림 구독 및 발행 API

## 개요

Subscription 서비스의 알림 도메인은 숙박시설 및 키오스크의 실시간 이벤트를 구독하고 발행하는 기능을 제공합니다. GraphQL Subscription을 통해 WebSocket 기반 실시간 통신을 지원하며, 다양한 우선순위와 타입의 알림을 처리할 수 있습니다.

## 타입 정의

### AccommodationNotification

숙박시설 알림 객체입니다.

```graphql theme={null}
type AccommodationNotification {
  id: ID!
  accommodationId: ID
  type: String
  data: String
  createdAt: Date
  priority: Int
  roomTypeId: ID
  description: String
  template: String
  objective: String
  relative: String

  # 계산된 필드
  dotType: String
}
```

#### 필드 설명

<ResponseField name="id" type="ID!" required>
  알림 고유 식별자 (ULID)
</ResponseField>

<ResponseField name="accommodationId" type="ID">
  알림이 속한 숙박시설 ID
</ResponseField>

<ResponseField name="type" type="String">
  알림 유형 (예: `"emergency"`, `"info"`, `"warning"`)
</ResponseField>

<ResponseField name="data" type="String">
  알림 관련 데이터 (JSON 문자열)

  * 객실 정보, 고객 정보 등 알림 상세 내용 포함
</ResponseField>

<ResponseField name="createdAt" type="Date">
  알림 생성 시각
</ResponseField>

<ResponseField name="priority" type="Int">
  알림 우선순위

  * `1`: 일반 (초록)
  * `2`: 경고 (노랑)
  * `3+`: 긴급 (빨강)
</ResponseField>

<ResponseField name="roomTypeId" type="ID">
  관련 객실 유형 ID
</ResponseField>

<ResponseField name="description" type="String">
  알림 설명 또는 키오스크 ID
</ResponseField>

<ResponseField name="template" type="String">
  알림 템플릿 식별자

  * `ROOM_CHECKED_IN`: 체크인 완료
  * `RESERVATION_MISSED_HANDLED`: 예약 누락 처리
  * `AUTH_REQUESTED`: 인증 요청
  * `AUTH_REQUEST_CHECKIN`: 체크인 인증 요청
  * `AUTH_REQUEST_CHECKOUT`: 체크아웃 인증 요청
</ResponseField>

<ResponseField name="objective" type="String">
  알림 대상 또는 목적 (예: `"checkin"`, `"checkout"`)
</ResponseField>

<ResponseField name="relative" type="String">
  관련 엔티티 (예: 객실 ID, 예약 ID)
</ResponseField>

<ResponseField name="dotType" type="String">
  우선순위 기반 표시 타입 (계산된 필드)

  * `"success"`: priority = 1
  * `"warning"`: priority = 2
  * `"error"`: priority >= 3
  * `"disabled"`: priority = null
</ResponseField>

### KioskSystemNotification

키오스크 시스템 알림 객체입니다.

```graphql theme={null}
type KioskSystemNotification {
  id: ID!
  kioskId: ID!
  type: String!
  data: String!
  createdAt: Date!
}
```

<ResponseField name="id" type="ID!" required>
  시스템 알림 고유 식별자
</ResponseField>

<ResponseField name="kioskId" type="ID!" required>
  알림을 받을 키오스크 ID
</ResponseField>

<ResponseField name="type" type="String!" required>
  시스템 알림 유형
</ResponseField>

<ResponseField name="data" type="String!" required>
  시스템 알림 데이터 (JSON 문자열)
</ResponseField>

<ResponseField name="createdAt" type="Date!" required>
  알림 생성 시각
</ResponseField>

### DataEvent

데이터 이벤트 객체입니다.

```graphql theme={null}
type DataEvent {
  type: String!
  data: String!
}
```

<ResponseField name="type" type="String!" required>
  이벤트 타입

  * `RoomState`: 객실 상태 변경
  * `AuthRequest`: 인증 요청
  * `AuthRequestCheckin`: 체크인 인증 요청
  * `AuthRequestCheckout`: 체크아웃 인증 요청
</ResponseField>

<ResponseField name="data" type="String!" required>
  이벤트 데이터 (JSON 문자열)
</ResponseField>

## Queries

### getAccommodationNotifications

숙박시설의 알림 목록을 페이지네이션하여 조회합니다.

#### GraphQL Signature

```graphql theme={null}
query GetAccommodationNotifications(
  $accommodationId: ID!
  $first: Int
  $last: Int
  $after: String
  $before: String
  $filter: AccommodationNotificationQueryFilter
) {
  getAccommodationNotifications(
    accommodationId: $accommodationId
    first: $first
    last: $last
    after: $after
    before: $before
    filter: $filter
  ) {
    edges {
      cursor
      node {
        id
        type
        priority
        dotType
        description
        template
        objective
        relative
        roomTypeId
        data
        createdAt
      }
    }
    pageInfo {
      hasNextPage
      hasPreviousPage
      startCursor
      endCursor
    }
    totalCount
  }
}
```

#### 파라미터

<ParamField path="accommodationId" type="ID!" required>
  조회할 숙박시설 ID
</ParamField>

<ParamField path="first" type="Int">
  처음부터 가져올 항목 수 (forward pagination)
</ParamField>

<ParamField path="last" type="Int">
  끝에서부터 가져올 항목 수 (backward pagination)
</ParamField>

<ParamField path="after" type="String">
  이 커서 이후의 항목들을 가져옴
</ParamField>

<ParamField path="before" type="String">
  이 커서 이전의 항목들을 가져옴
</ParamField>

<ParamField path="filter" type="AccommodationNotificationQueryFilter">
  필터 조건:

  * `type`: 알림 유형
  * `dateFrom`: 시작 날짜
  * `dateTo`: 종료 날짜
  * `objective`: 목적
  * `relative`: 관련 엔티티
  * `roomTypeId`: 객실 유형 ID
  * `roomId`: 객실 ID
  * `sortKey`: 정렬 키
  * `sortDirection`: 정렬 방향 (`"asc"` 또는 `"desc"`)
</ParamField>

#### 응답

<ResponseField name="edges" type="[AccommodationNotificationEdge]!" required>
  알림 엣지 배열
</ResponseField>

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

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

#### 권한

* **필수 권한**: `MAID` 이상
* 해당 숙박시설에 대한 관리 권한 필요

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  query {
    getAccommodationNotifications(
      accommodationId: "01HQKS9V8X"
      first: 20
      filter: {
        type: "emergency"
        dateFrom: "2025-01-01T00:00:00Z"
        dateTo: "2025-01-31T23:59:59Z"
        sortKey: "createdAt"
        sortDirection: "desc"
      }
    ) {
      edges {
        node {
          id
          type
          priority
          dotType
          template
          description
          createdAt
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
      totalCount
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "getAccommodationNotifications": {
        "edges": [
          {
            "node": {
              "id": "01HQKS9V8X2N3P4Q5R",
              "type": "emergency",
              "priority": 3,
              "dotType": "error",
              "template": "RESERVATION_MISSED_HANDLED",
              "description": "예약 누락 발생",
              "createdAt": "2025-01-15T14:30:00Z"
            }
          },
          {
            "node": {
              "id": "01HQKS9V8X2N3P4Q5S",
              "type": "info",
              "priority": 1,
              "dotType": "success",
              "template": "ROOM_CHECKED_IN",
              "description": "객실 체크인 완료",
              "createdAt": "2025-01-15T10:00:00Z"
            }
          }
        ],
        "pageInfo": {
          "hasNextPage": true,
          "endCursor": "Y3Vyc29yOjE="
        },
        "totalCount": 45
      }
    }
  }
  ```
</CodeGroup>

## Mutations

### publishAccommodationNotification

숙박시설 알림을 생성하고 실시간으로 구독자들에게 전송합니다.

#### GraphQL Signature

```graphql theme={null}
mutation PublishAccommodationNotification(
  $input: AddAccommodationNotificationInput!
) {
  publishAccommodationNotification(input: $input) {
    id
    accommodationId
    type
    priority
    dotType
    template
    description
    data
    createdAt
  }
}
```

#### 파라미터

<ParamField path="input" type="AddAccommodationNotificationInput!" required>
  알림 생성 입력:

  * `accommodationId` (ID!, required): 숙박시설 ID
  * `type` (String!, required): 알림 유형
  * `priority` (Int): 우선순위 (1-3+)
  * `data` (String): 알림 데이터 (JSON)
  * `objective` (String): 목적
  * `relative` (String): 관련 엔티티
  * `template` (String): 템플릿 식별자
  * `description` (String): 설명
  * `roomTypeId` (ID): 객실 유형 ID
</ParamField>

#### 응답

<ResponseField name="AccommodationNotification" type="AccommodationNotification!" required>
  생성된 알림 객체
</ResponseField>

#### 권한

* **필수 권한**: 해당 숙박시설에 대한 관리 권한
* 알림 발행 후 자동으로 구독자들에게 실시간 전송

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  mutation {
    publishAccommodationNotification(
      input: {
        accommodationId: "01HQKS9V8X"
        type: "emergency"
        priority: 3
        template: "RESERVATION_MISSED_HANDLED"
        description: "301호 예약 누락"
        objective: "room"
        relative: "01HQKS9V8Y"
        roomTypeId: "01HQKS9V8Z"
        data: "{\"roomNumber\": \"301\", \"guestName\": \"홍길동\"}"
      }
    ) {
      id
      type
      priority
      dotType
      createdAt
    }
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "publishAccommodationNotification": {
        "id": "01HQKS9V8X2N3P4Q5T",
        "type": "emergency",
        "priority": 3,
        "dotType": "error",
        "createdAt": "2025-01-15T14:35:00Z"
      }
    }
  }
  ```
</CodeGroup>

#### 동작

1. EventStore에 알림 이벤트 생성
2. Saga 처리를 통해 데이터베이스에 저장
3. PubSub을 통해 실시간 구독자들에게 전송
4. 템플릿이 `AUTH_REQUESTED`인 경우 카카오톡 알림톡 자동 발송

***

### publishKioskSystemNoficiation

키오스크 시스템 알림을 발행합니다.

<Note>
  함수명에 오타가 있습니다 (`Noficiation` → `Notification`). 호환성 유지를 위해 현재 이름 사용 중입니다.
</Note>

#### GraphQL Signature

```graphql theme={null}
mutation PublishKioskSystemNotification(
  $input: AddKioskSystemNotificationInput!
) {
  publishKioskSystemNoficiation(input: $input)
}
```

#### 파라미터

<ParamField path="input" type="AddKioskSystemNotificationInput!" required>
  키오스크 시스템 알림 입력:

  * `kioskId` (ID!, required): 키오스크 ID
  * `type` (String): 알림 유형
  * `data` (String): 알림 데이터 (JSON)
  * `createdAt` (Date): 생성 시각
</ParamField>

#### 응답

<ResponseField name="success" type="Boolean!" required>
  알림 발행 성공 여부
</ResponseField>

#### 권한

* **필수 권한**: `ADMIN` (관리자만 시스템 알림 발행 가능)

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  mutation {
    publishKioskSystemNoficiation(
      input: {
        kioskId: "01HQKS9V8X"
        type: "restart"
        data: "{\"reason\": \"system_update\"}"
      }
    )
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "publishKioskSystemNoficiation": true
    }
  }
  ```
</CodeGroup>

***

### handleEmergencyNotification

긴급 알림을 처리하고 상태를 업데이트합니다.

#### GraphQL Signature

```graphql theme={null}
mutation HandleEmergencyNotification(
  $input: HandleEmergencyNotificationInput!
) {
  handleEmergencyNotification(input: $input)
}
```

#### 파라미터

<ParamField path="input" type="HandleEmergencyNotificationInput!" required>
  긴급 알림 처리 입력:

  * `accommodationId` (ID!, required): 숙박시설 ID
  * `notificationId` (ID!, required): 알림 ID
</ParamField>

#### 응답

<ResponseField name="success" type="Boolean!" required>
  처리 성공 여부
</ResponseField>

#### 권한

* **필수 권한**: 해당 숙박시설에 대한 관리 권한

#### 동작

1. 해당 알림을 조회
2. 템플릿을 `RESERVATION_MISSED_HANDLED`로 변경
3. 우선순위를 1(일반)로 낮춤
4. 구독자들에게 업데이트된 알림 전송

#### 예제

<CodeGroup>
  ```graphql Request theme={null}
  mutation {
    handleEmergencyNotification(
      input: {
        accommodationId: "01HQKS9V8X"
        notificationId: "01HQKS9V8X2N3P4Q5T"
      }
    )
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "handleEmergencyNotification": true
    }
  }
  ```
</CodeGroup>

## Subscriptions

### accommodationSubscription

숙박시설의 실시간 알림을 구독합니다.

#### GraphQL Signature

```graphql theme={null}
subscription OnAccommodationNotification($accommodationId: ID!) {
  accommodationSubscription(accommodationId: $accommodationId) {
    id
    accommodationId
    type
    priority
    dotType
    template
    description
    objective
    relative
    roomTypeId
    data
    createdAt
  }
}
```

#### 파라미터

<ParamField path="accommodationId" type="ID!" required>
  구독할 숙박시설 ID
</ParamField>

#### 응답

<ResponseField name="AccommodationNotification" type="AccommodationNotification!" required>
  실시간으로 수신되는 알림 객체
</ResponseField>

#### 권한

* **필수 권한**: `MAID` 이상
* 해당 숙박시설에 대한 관리 권한 필요

#### 예제

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { gql, useSubscription } from '@apollo/client';

  const ACCOMMODATION_SUBSCRIPTION = gql`
    subscription OnAccommodationNotification($accommodationId: ID!) {
      accommodationSubscription(accommodationId: $accommodationId) {
        id
        type
        priority
        dotType
        template
        description
        createdAt
        data
      }
    }
  `;

  function NotificationListener({ accommodationId }: { accommodationId: string }) {
    const { data, loading, error } = useSubscription(
      ACCOMMODATION_SUBSCRIPTION,
      {
        variables: { accommodationId },
      }
    );

    useEffect(() => {
      if (data?.accommodationSubscription) {
        const notification = data.accommodationSubscription;

        // 우선순위별 처리
        switch (notification.dotType) {
          case 'error':
            toast.error(notification.description);
            playUrgentSound();
            break;
          case 'warning':
            toast.warning(notification.description);
            break;
          case 'success':
            toast.success(notification.description);
            break;
        }
      }
    }, [data]);

    if (loading) return <p>구독 연결 중...</p>;
    if (error) return <p>구독 에러: {error.message}</p>;

    return <NotificationDisplay notification={data?.accommodationSubscription} />;
  }
  ```

  ```graphql Subscription theme={null}
  subscription {
    accommodationSubscription(accommodationId: "01HQKS9V8X") {
      id
      type
      priority
      dotType
      template
      description
      createdAt
    }
  }
  ```
</CodeGroup>

#### 수신 이벤트 예시

```json theme={null}
{
  "data": {
    "accommodationSubscription": {
      "id": "01HQKS9V8X2N3P4Q5U",
      "type": "info",
      "priority": 1,
      "dotType": "success",
      "template": "ROOM_CHECKED_IN",
      "description": "302호 체크인 완료",
      "createdAt": "2025-01-15T15:00:00Z"
    }
  }
}
```

***

### dataSubscription

숙박시설의 실시간 데이터 이벤트를 구독합니다.

#### GraphQL Signature

```graphql theme={null}
subscription OnDataEvent($accommodationId: ID!) {
  dataSubscription(accommodationId: $accommodationId) {
    type
    data
  }
}
```

#### 파라미터

<ParamField path="accommodationId" type="ID!" required>
  구독할 숙박시설 ID
</ParamField>

#### 응답

<ResponseField name="DataEvent" type="DataEvent!" required>
  실시간으로 수신되는 데이터 이벤트
</ResponseField>

#### 권한

* **필수 권한**: `MAID` 이상
* 해당 숙박시설에 대한 관리 권한 필요

#### 이벤트 타입

| type                  | 설명         | data 형식                                                         |
| --------------------- | ---------- | --------------------------------------------------------------- |
| `RoomState`           | 객실 상태 변경   | `{ id: string, stateUpdatedAt: number }`                        |
| `AuthRequest`         | 인증 요청      | `{ kioskId: string, notificationId: string, iat: number, ... }` |
| `AuthRequestCheckin`  | 체크인 인증 요청  | `{ kioskId: string, iat: number, ... }`                         |
| `AuthRequestCheckout` | 체크아웃 인증 요청 | `{ kioskId: string, iat: number, ... }`                         |

#### 예제

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { gql, useSubscription } from '@apollo/client';

  const DATA_SUBSCRIPTION = gql`
    subscription OnDataEvent($accommodationId: ID!) {
      dataSubscription(accommodationId: $accommodationId) {
        type
        data
      }
    }
  `;

  function DataEventListener({ accommodationId }: { accommodationId: string }) {
    const { data } = useSubscription(DATA_SUBSCRIPTION, {
      variables: { accommodationId },
    });

    useEffect(() => {
      if (data?.dataSubscription) {
        const event = data.dataSubscription;
        const eventData = JSON.parse(event.data);

        switch (event.type) {
          case 'RoomState':
            // 객실 상태 업데이트
            updateRoomState(eventData.id, eventData.stateUpdatedAt);
            break;

          case 'AuthRequest':
            // 인증 요청 처리
            handleAuthRequest({
              kioskId: eventData.kioskId,
              notificationId: eventData.notificationId,
              ...eventData,
            });
            break;

          case 'AuthRequestCheckin':
            // 체크인 인증 요청
            showAuthDialog('checkin', eventData);
            break;

          case 'AuthRequestCheckout':
            // 체크아웃 인증 요청
            showAuthDialog('checkout', eventData);
            break;

          default:
            console.log('Unknown event type:', event.type);
        }
      }
    }, [data]);

    return null;
  }
  ```

  ```graphql Subscription theme={null}
  subscription {
    dataSubscription(accommodationId: "01HQKS9V8X") {
      type
      data
    }
  }
  ```
</CodeGroup>

#### 수신 이벤트 예시

```json theme={null}
// 객실 상태 변경
{
  "data": {
    "dataSubscription": {
      "type": "RoomState",
      "data": "{\"id\":\"01HQKS9V8Y\",\"stateUpdatedAt\":1705329600000}"
    }
  }
}

// 인증 요청
{
  "data": {
    "dataSubscription": {
      "type": "AuthRequest",
      "data": "{\"kioskId\":\"01HQKS9V8X\",\"notificationId\":\"01HQKS9V8Z\",\"accommodationName\":\"벤딧 호텔\",\"name\":\"301\",\"iat\":1705329600000}"
    }
  }
}
```

***

### kioskSystemSubscription

키오스크 시스템 알림을 구독합니다.

#### GraphQL Signature

```graphql theme={null}
subscription OnKioskSystemNotification($kioskId: ID!) {
  kioskSystemSubscription(kioskId: $kioskId) {
    id
    kioskId
    type
    data
    createdAt
  }
}
```

#### 파라미터

<ParamField path="kioskId" type="ID!" required>
  구독할 키오스크 ID
</ParamField>

#### 응답

<ResponseField name="KioskSystemNotification" type="KioskSystemNotification!" required>
  실시간으로 수신되는 키오스크 시스템 알림
</ResponseField>

#### 권한

* **필수 권한**: 키오스크 자체 인증
* 해당 키오스크 ID와 토큰 일치 확인

#### 동작

1. 구독 시작 시 키오스크 연결 상태를 `connected`로 업데이트
2. WebSocket 연결 유지 중 시스템 알림 수신
3. 연결 해제 시 자동으로 연결 상태 업데이트

#### 예제

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { gql, useSubscription } from '@apollo/client';

  const KIOSK_SYSTEM_SUBSCRIPTION = gql`
    subscription OnKioskSystemNotification($kioskId: ID!) {
      kioskSystemSubscription(kioskId: $kioskId) {
        id
        type
        data
        createdAt
      }
    }
  `;

  function KioskSystemListener({ kioskId }: { kioskId: string }) {
    const { data } = useSubscription(KIOSK_SYSTEM_SUBSCRIPTION, {
      variables: { kioskId },
    });

    useEffect(() => {
      if (data?.kioskSystemSubscription) {
        const notification = data.kioskSystemSubscription;
        const systemData = JSON.parse(notification.data);

        switch (notification.type) {
          case 'restart':
            // 키오스크 재시작
            handleRestart(systemData);
            break;
          case 'update':
            // 시스템 업데이트
            handleUpdate(systemData);
            break;
          case 'config':
            // 설정 변경
            updateConfig(systemData);
            break;
          default:
            console.log('Unknown system notification:', notification.type);
        }
      }
    }, [data]);

    return null;
  }
  ```

  ```graphql Subscription theme={null}
  subscription {
    kioskSystemSubscription(kioskId: "01HQKS9V8X") {
      id
      type
      data
      createdAt
    }
  }
  ```
</CodeGroup>

## 사용 시나리오

### 시나리오 1: 대시보드 실시간 알림

대시보드에서 숙박시설의 모든 이벤트를 실시간으로 모니터링합니다.

```typescript theme={null}
// 1. 숙박시설 알림 구독
useSubscription(ACCOMMODATION_SUBSCRIPTION, {
  variables: { accommodationId: '01HQKS9V8X' },
  onData: ({ data }) => {
    const notification = data.data.accommodationSubscription;

    // 우선순위별 알림 표시
    if (notification.priority >= 3) {
      showUrgentAlert(notification);
      playSound('urgent');
    } else {
      showToast(notification);
    }

    // 알림 목록 업데이트
    addToNotificationList(notification);
  },
});

// 2. 데이터 이벤트 구독 (객실 상태 실시간 업데이트)
useSubscription(DATA_SUBSCRIPTION, {
  variables: { accommodationId: '01HQKS9V8X' },
  onData: ({ data }) => {
    const event = data.data.dataSubscription;

    if (event.type === 'RoomState') {
      const { id, stateUpdatedAt } = JSON.parse(event.data);
      updateRoomStateInUI(id, stateUpdatedAt);
    }
  },
});
```

### 시나리오 2: 키오스크 인증 요청 처리

키오스크에서 인증 요청을 보내고 대시보드에서 처리합니다.

```typescript theme={null}
// 키오스크: 인증 요청 발행
await publishAccommodationNotification({
  accommodationId: '01HQKS9V8X',
  type: 'auth',
  priority: 2,
  template: 'AUTH_REQUESTED',
  description: kioskId,
  data: JSON.stringify({
    accommodationName: '벤딧 호텔',
    name: '301',
  }),
});

// 대시보드: 데이터 구독으로 인증 요청 수신
useSubscription(DATA_SUBSCRIPTION, {
  variables: { accommodationId: '01HQKS9V8X' },
  onData: ({ data }) => {
    const event = data.data.dataSubscription;

    if (event.type === 'AuthRequest') {
      const authData = JSON.parse(event.data);
      showAuthRequestDialog({
        kioskId: authData.kioskId,
        roomNumber: authData.name,
        notificationId: authData.notificationId,
      });
    }
  },
});

// 대시보드: 인증 승인 후 처리
await handleEmergencyNotification({
  accommodationId: '01HQKS9V8X',
  notificationId: authData.notificationId,
});
```

### 시나리오 3: 긴급 알림 처리

예약 누락과 같은 긴급 상황을 처리합니다.

```typescript theme={null}
// 1. 긴급 알림 발행
const notification = await publishAccommodationNotification({
  accommodationId: '01HQKS9V8X',
  type: 'emergency',
  priority: 3,
  template: 'RESERVATION_MISSED',
  description: '301호 예약 누락',
  objective: 'room',
  relative: roomId,
  data: JSON.stringify({
    roomNumber: '301',
    guestName: '홍길동',
  }),
});

// 2. 대시보드에서 즉시 수신 및 표시
// (accommodationSubscription으로 자동 수신)

// 3. 담당자가 처리 완료
await handleEmergencyNotification({
  accommodationId: '01HQKS9V8X',
  notificationId: notification.id,
});

// 4. 모든 구독자에게 업데이트된 알림 전송
// (우선순위 1로 변경, 템플릿 RESERVATION_MISSED_HANDLED)
```

## 에러 처리

### 권한 부족

```json theme={null}
{
  "errors": [
    {
      "message": "Insufficient permissions",
      "extensions": {
        "code": "FORBIDDEN"
      }
    }
  ]
}
```

### 존재하지 않는 알림

```json theme={null}
{
  "errors": [
    {
      "message": "Notification not found",
      "extensions": {
        "code": "NOT_FOUND"
      }
    }
  ]
}
```

### WebSocket 연결 실패

클라이언트에서 재연결 로직을 구현해야 합니다:

```typescript theme={null}
const wsLink = new GraphQLWsLink(
  createClient({
    url: 'wss://development.vpms.io/graphql',
    connectionParams: {
      authorization: `Bearer ${accessToken}`,
    },
    retryAttempts: 5,
    retryWait: async (retries) => {
      await new Promise(resolve =>
        setTimeout(resolve, Math.min(1000 * 2 ** retries, 30000))
      );
    },
  })
);
```

## 성능 최적화

### 필터링 활용

불필요한 알림을 필터링하여 네트워크 트래픽을 줄입니다:

```graphql theme={null}
query {
  getAccommodationNotifications(
    accommodationId: "01HQKS9V8X"
    first: 50
    filter: {
      type: "emergency"
      priority: 3
      dateFrom: "2025-01-01T00:00:00Z"
    }
  ) {
    edges {
      node {
        id
        description
        createdAt
      }
    }
  }
}
```

### 구독 최적화

필요한 필드만 요청하여 데이터 전송량을 최소화합니다:

```graphql theme={null}
subscription {
  accommodationSubscription(accommodationId: "01HQKS9V8X") {
    id
    priority
    dotType
    description
    # data 필드는 필요시에만 요청
  }
}
```

## 관련 API

* [Core Service - 예약 API](/api-reference/core-svc/reservation) - 예약 관련 이벤트 생성
* [User Service](/api-reference/user-svc/introduction) - 사용자 권한 관리
