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

# Subscription Service API

> 실시간 구독 및 알림 서비스 API 레퍼런스

## 개요

Subscription Service는 VPMS 클러스터의 실시간 데이터 구독 및 알림 시스템을 담당하는 서비스입니다. GraphQL Subscription을 통해 숙박시설 관련 이벤트를 실시간으로 전달하며, 키오스크 및 대시보드 간의 실시간 통신을 지원합니다.

## 주요 기능

<CardGroup cols={2}>
  <Card title="실시간 알림" icon="bell">
    숙박시설 이벤트 실시간 구독 및 알림
  </Card>

  <Card title="키오스크 통신" icon="desktop">
    키오스크 시스템 상태 및 명령 실시간 전달
  </Card>

  <Card title="이벤트 발행" icon="paper-plane">
    숙박시설 및 키오스크 알림 발행
  </Card>

  <Card title="데이터 스트림" icon="stream">
    객실 상태, 인증 요청 등 실시간 데이터 스트림
  </Card>
</CardGroup>

## API 도메인

### 알림 관리 (Notification)

실시간 알림 구독 및 발행 기능을 제공합니다.

* **[알림 API](/api-reference/subscription-svc/notification)**: 숙박시설 알림, 키오스크 시스템 알림, 데이터 구독

## 기술 스택

* **API 타입**: GraphQL (Apollo Federation) + WebSocket
* **프레임워크**: Fastify + TypeScript
* **데이터베이스**: PostgreSQL + Prisma ORM
* **실시간 통신**: GraphQL Subscription (PubSub)
* **이벤트 소싱**: EventStore

## GraphQL Endpoint

### HTTP Endpoint

```
POST https://development.vpms.io/graphql
```

### WebSocket Endpoint (Subscription)

```
WS wss://development.vpms.io/graphql
```

<Info>
  GraphQL 공통 설정 및 인증 방법은 [GraphQL 설정 가이드](/api-reference/introduction)를 참고하세요.
</Info>

Subscription Service는 Apollo Federation의 서브그래프로 동작하며, API Gateway를 통해 통합된 스키마로 접근할 수 있습니다.

## 실시간 구독 흐름

<Steps>
  <Step title="WebSocket 연결">
    GraphQL Subscription을 위한 WebSocket 연결 수립
  </Step>

  <Step title="구독 시작">
    [accommodationSubscription](/api-reference/subscription-svc/notification#accommodationsubscription) 또는 [dataSubscription](/api-reference/subscription-svc/notification#datasubscription)으로 실시간 구독 시작
  </Step>

  <Step title="이벤트 수신">
    서버에서 발생하는 이벤트를 실시간으로 수신
  </Step>

  <Step title="알림 처리">
    수신된 알림을 UI에 표시하거나 후속 처리 수행
  </Step>
</Steps>

## 예제: 실시간 알림 구독

<CodeGroup>
  ```typescript TypeScript (Apollo Client) theme={null}
  import { ApolloClient, InMemoryCache, split, HttpLink } from '@apollo/client';
  import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
  import { createClient } from 'graphql-ws';
  import { getMainDefinition } from '@apollo/client/utilities';
  import { gql } from '@apollo/client';

  // HTTP 링크 설정
  const httpLink = new HttpLink({
    uri: 'https://development.vpms.io/graphql',
    headers: {
      authorization: `Bearer ${accessToken}`,
    },
  });

  // WebSocket 링크 설정
  const wsLink = new GraphQLWsLink(
    createClient({
      url: 'wss://development.vpms.io/graphql',
      connectionParams: {
        authorization: `Bearer ${accessToken}`,
      },
    })
  );

  // HTTP와 WebSocket 링크 분기
  const splitLink = split(
    ({ query }) => {
      const definition = getMainDefinition(query);
      return (
        definition.kind === 'OperationDefinition' &&
        definition.operation === 'subscription'
      );
    },
    wsLink,
    httpLink
  );

  const client = new ApolloClient({
    link: splitLink,
    cache: new InMemoryCache(),
  });

  // 숙박시설 알림 구독
  const ACCOMMODATION_SUBSCRIPTION = gql`
    subscription OnAccommodationNotification($accommodationId: ID!) {
      accommodationSubscription(accommodationId: $accommodationId) {
        id
        type
        priority
        dotType
        description
        template
        objective
        relative
        roomTypeId
        createdAt
        data
      }
    }
  `;

  // 구독 시작
  const subscription = client.subscribe({
    query: ACCOMMODATION_SUBSCRIPTION,
    variables: {
      accommodationId: '01HQKS9V8X',
    },
  }).subscribe({
    next: ({ data }) => {
      console.log('새 알림:', data.accommodationSubscription);

      // 우선순위에 따른 처리
      const notification = data.accommodationSubscription;
      if (notification.priority >= 3) {
        // 긴급 알림 (빨강)
        showUrgentAlert(notification);
      } else if (notification.priority === 2) {
        // 경고 알림 (노랑)
        showWarning(notification);
      } else {
        // 일반 알림 (초록)
        showInfo(notification);
      }
    },
    error: (error) => {
      console.error('구독 에러:', error);
    },
  });

  // 데이터 구독 (객실 상태, 인증 요청 등)
  const DATA_SUBSCRIPTION = gql`
    subscription OnDataEvent($accommodationId: ID!) {
      dataSubscription(accommodationId: $accommodationId) {
        type
        data
      }
    }
  `;

  client.subscribe({
    query: DATA_SUBSCRIPTION,
    variables: {
      accommodationId: '01HQKS9V8X',
    },
  }).subscribe({
    next: ({ data }) => {
      const event = data.dataSubscription;
      const eventData = JSON.parse(event.data);

      switch (event.type) {
        case 'RoomState':
          updateRoomState(eventData.id, eventData.stateUpdatedAt);
          break;
        case 'AuthRequest':
          handleAuthRequest(eventData);
          break;
        default:
          console.log('알 수 없는 이벤트:', event.type);
      }
    },
  });
  ```

  ```javascript JavaScript (Vanilla) theme={null}
  // WebSocket 연결 설정
  const wsProtocol = {
    'graphql-transport-ws': true,
  };

  const socket = new WebSocket(
    'wss://development.vpms.io/graphql',
    'graphql-transport-ws'
  );

  socket.onopen = () => {
    // 연결 초기화
    socket.send(JSON.stringify({
      type: 'connection_init',
      payload: {
        authorization: `Bearer ${accessToken}`,
      },
    }));
  };

  socket.onmessage = (event) => {
    const message = JSON.parse(event.data);

    if (message.type === 'connection_ack') {
      // 연결 성공, 구독 시작
      socket.send(JSON.stringify({
        id: '1',
        type: 'subscribe',
        payload: {
          query: `
            subscription OnAccommodationNotification($accommodationId: ID!) {
              accommodationSubscription(accommodationId: $accommodationId) {
                id
                type
                priority
                dotType
                description
                template
                createdAt
              }
            }
          `,
          variables: {
            accommodationId: '01HQKS9V8X',
          },
        },
      }));
    } else if (message.type === 'next') {
      // 새 알림 수신
      const notification = message.payload.data.accommodationSubscription;
      console.log('새 알림:', notification);
      displayNotification(notification);
    } else if (message.type === 'error') {
      console.error('구독 에러:', message.payload);
    }
  };

  socket.onerror = (error) => {
    console.error('WebSocket 에러:', error);
  };

  socket.onclose = () => {
    console.log('WebSocket 연결 종료');
    // 재연결 로직
    setTimeout(reconnect, 5000);
  };
  ```
</CodeGroup>

## 알림 타입

Subscription Service는 다양한 유형의 알림을 지원합니다:

### 숙박시설 알림 (AccommodationNotification)

* **체크인 알림**: 고객 체크인 완료 시
* **긴급 알림**: 예약 누락, 시스템 오류 등
* **인증 요청**: 키오스크 인증 요청
* **객실 상태 변경**: 청소 완료, 정비 필요 등

### 키오스크 시스템 알림 (KioskSystemNotification)

* **시스템 명령**: 키오스크 제어 명령
* **상태 업데이트**: 연결 상태, 동작 상태

### 데이터 이벤트 (DataEvent)

* **객실 상태**: 실시간 객실 상태 변경
* **인증 이벤트**: 인증 요청 및 처리 상태

## 우선순위 시스템

알림은 우선순위(priority)에 따라 분류됩니다:

| 우선순위 | dotType  | 설명  | 용도               |
| ---- | -------- | --- | ---------------- |
| 1    | success  | 일반  | 정보성 알림, 정상 처리 완료 |
| 2    | warning  | 경고  | 주의가 필요한 상황       |
| 3+   | error    | 긴급  | 즉각적인 조치가 필요한 상황  |
| null | disabled | 비활성 | 처리 완료된 알림        |

## 알림 템플릿

자주 사용되는 알림 템플릿:

* `ROOM_CHECKED_IN`: 객실 체크인 완료
* `RESERVATION_MISSED_HANDLED`: 예약 누락 처리 완료
* `AUTH_REQUESTED`: 키오스크 인증 요청
* `AUTH_REQUEST_CHECKIN`: 체크인 인증 요청
* `AUTH_REQUEST_CHECKOUT`: 체크아웃 인증 요청

## 보안 및 권한

### 숙박시설 알림 구독

* 권한: `MAID` 이상 (청소 담당자 권한)
* 해당 숙박시설에 대한 관리 권한 필요

### 키오스크 시스템 구독

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

### 데이터 구독

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

## 연결 상태 관리

Subscription Service는 키오스크의 연결 상태를 자동으로 관리합니다:

* **연결 시**: 키오스크 연결 상태 `connected`로 업데이트
* **구독 활성화**: WebSocket 연결 유지 중 실시간 알림 수신
* **연결 해제 시**: 자동으로 연결 상태 업데이트

<Warning>
  WebSocket 연결이 끊어지면 자동으로 재연결하는 로직을 클라이언트에 구현해야 합니다.
</Warning>

## 관련 서비스

* [Core Service](/api-reference/core-svc/introduction) - 예약 및 객실 상태 관리
* [User Service](/api-reference/user-svc/introduction) - 사용자 인증 및 권한 관리

## 지원

문의사항이 있으시면 [support@vendit.io](mailto:support@vendit.io)로 연락주세요.
