npm package: @back23/promptly-sdk
Description: Promptly AI CMS SDK for JavaScript/TypeScript
Author: jiahnpark
Latest version: 2.18.1
Deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.

# @back23/promptly-sdk

Promptly AI CMS 공식 SDK - 헤드리스 CMS + 이커머스 + AI 기능을 제공합니다.

**버전: 2.18.0**

## 설치

```bash
npm install @back23/promptly-sdk
```

## 빠른 시작

```typescript
import { Promptly } from '@back23/promptly-sdk';

const client = new Promptly({
  tenantId: 'your-tenant-id',
  apiKey: 'pky_xxxxxxxxxxxxxxxx',  // 필수 - Dashboard > Settings > API Tokens
  baseUrl: 'https://promptly.webbyon.com',  // 선택

  // 토큰 자동 저장 (로그인 유지)
  persistToken: true,
  storageType: 'localStorage',  // 또는 'sessionStorage'
  onAuthStateChange: (token, user) => {
    console.log('Auth changed:', user);
  },
});

// 블로그 조회
const { data: posts } = await client.blog.list();

// 상품 조회
const { data: products } = await client.shop.listProducts();
```

---

## 인증 (Auth)

### 기본 설정

```typescript
const client = new Promptly({
  tenantId: 'demo',
  apiKey: 'pky_xxxxxxxxxxxxxxxx',  // 필수
  persistToken: true,  // 토큰 자동 저장
});
```

### 토큰 자동 저장

`persistToken: true` 설정 시:
- **로그인**: `localStorage`에 자동 저장
- **새로고침**: 토큰 자동 복원
- **로그아웃**: 토큰 자동 삭제
- **저장 키**: `promptly_auth_token_{tenantId}`

```typescript
const client = new Promptly({
  tenantId: 'demo',
  apiKey: 'pky_xxx',
  persistToken: true,
  storageType: 'localStorage',  // 기본값, 또는 'sessionStorage'
  onAuthStateChange: (token, user) => {
    // 로그인/로그아웃 시 호출
    if (token) {
      console.log('Logged in:', user);
    } else {
      console.log('Logged out');
    }
  },
});
```

### 로그인/회원가입

```typescript
// 회원가입
await client.auth.register({
  name: 'John Doe',
  email: 'john@example.com',
  password: 'password',
  password_confirmation: 'password',
});

// 로그인 (토큰 자동 저장됨)
const { member, token } = await client.auth.login({
  email: 'john@example.com',
  password: 'password',
});

// 로그아웃 (토큰 자동 삭제됨)
await client.auth.logout();

// 인증 상태 확인
client.auth.isAuthenticated();  // true/false

// 현재 사용자
const me = await client.auth.me();

// 프로필 수정
await client.auth.updateProfile({ name: 'New Name' });
```

### 소셜 로그인

```typescript
// 사용 가능한 프로바이더
const providers = await client.auth.getSocialProviders();
// ['google', 'kakao', 'naver']

// 로그인 URL 획득
const { url } = await client.auth.getSocialAuthUrl('google');
window.location.href = url;

// 콜백 처리 (리다이렉트 후)
const { member, token } = await client.auth.socialCallback('google', code);
```

### 비밀번호 재설정

```typescript
// 재설정 이메일 발송
await client.auth.forgotPassword({ email: 'john@example.com' });

// 비밀번호 재설정
await client.auth.resetPassword({
  token: 'reset-token',
  email: 'john@example.com',
  password: 'newpassword',
  password_confirmation: 'newpassword',
});
```

---

## Blog API

```typescript
// 목록 조회
const { data, meta } = await client.blog.list({
  page: 1,
  per_page: 10,
  category: 'tech',
  tag: 'laravel',
  search: 'keyword',
});

// 상세 조회
const post = await client.blog.get('post-slug');

// 추천 글
const featured = await client.blog.featured(5);

// 카테고리/태그별 필터
const { data } = await client.blog.byCategory('news');
const { data } = await client.blog.byTag('featured');

// 카테고리/태그 목록
const categories = await client.blog.categories();  // string[]
const tags = await client.blog.tags();  // string[]
```

---

## Boards API

```typescript
// 게시판 목록
const { data: boards } = await client.boards.list();

// 게시판 조회
const board = await client.boards.get('board-slug');

// 게시글 목록
const { data: posts } = await client.boards.listPosts('board-slug', {
  page: 1,
  per_page: 10,
  search: 'keyword',
});

// 게시글 상세
const post = await client.boards.getPost(postId);

// 게시글 작성 (인증 필요)
await client.boards.createPost({
  board_id: 1,
  title: 'Title',
  content: 'Content',
  is_secret: false,
});

// 게시글 수정/삭제
await client.boards.updatePost(postId, { title: 'New Title' });
await client.boards.deletePost(postId);
```

---

## Comments API

게시판, 블로그, 독립 댓글(방명록) 지원

```typescript
// 게시판 댓글
const { data } = await client.comments.boardPost(postId);
await client.comments.createBoardPost(postId, {
  author_name: 'John',
  content: 'Great!',
  password: '1234',  // 비회원 댓글
});

// 블로그 댓글
const { data } = await client.comments.blogPost('post-slug');
await client.comments.createBlogPost('post-slug', {
  content: 'Nice article!',
});

// 독립 댓글 (방명록)
const { data } = await client.comments.standalone('guestbook');
await client.comments.createStandalone('guestbook', {
  author_name: 'Visitor',
  content: 'Hello!',
});

// 공통
await client.comments.update(commentId, { content: 'Updated' });
await client.comments.delete(commentId, { password: '1234' });
await client.comments.like(commentId);
```

---

## Shop API

### 상품

```typescript
// 상품 목록
const { data: products } = await client.shop.listProducts({
  category: 'shoes',
  is_featured: true,
  min_price: 10000,
  max_price: 50000,
  search: 'keyword',
});

// 상품 상세
const product = await client.shop.getProduct('product-slug');

// 추천 상품
const featured = await client.shop.featuredProducts(8);

// 카테고리 목록
const categories = await client.shop.listCategories();
```

### 상품 리뷰

```typescript
// 리뷰 목록
const { data: reviews, stats } = await client.shop.getProductReviews('product-slug', {
  page: 1,
  per_page: 10,
  rating: 5,  // 별점 필터
});

// 리뷰 통계
// stats = { average_rating: 4.5, total_count: 123, rating_counts: { 5: 80, 4: 30, ... } }

// 리뷰 작성 (인증 + 구매 필요)
await client.shop.createProductReview('product-slug', {
  rating: 5,
  title: 'Great product!',
  content: 'Highly recommended.',
  images: ['https://...'],
});

// 리뷰 수정/삭제
await client.shop.updateProductReview(reviewId, { rating: 4 });
await client.shop.deleteProductReview(reviewId);

// 도움이 됐어요
await client.shop.markReviewHelpful(reviewId);

// 내 리뷰 목록
const { data: myReviews } = await client.shop.getMyReviews();
```

### 장바구니 (인증 필요)

```typescript
// 장바구니 조회
const cart = await client.shop.getCart();

// 상품 추가
await client.shop.addToCart({
  product_id: 1,
  quantity: 2,
  variant_id: 3,  // 옵션 상품
});

// 수량 변경
await client.shop.updateCartItem(itemId, { quantity: 3 });

// 상품 제거
await client.shop.removeFromCart(itemId);

// 장바구니 비우기
await client.shop.clearCart();
```

### 주문 (인증 필요)

```typescript
// 주문 생성
const order = await client.shop.createOrder({
  orderer_name: 'John Doe',
  orderer_email: 'john@example.com',
  orderer_phone: '010-1234-5678',
  shipping_name: 'John Doe',
  shipping_phone: '010-1234-5678',
  shipping_zipcode: '12345',
  shipping_address: '서울시 강남구',
  shipping_address_detail: '101동 101호',
  shipping_memo: '부재시 경비실',
  coupon_code: 'SAVE10',
});

// 주문 목록
const { data: orders } = await client.shop.listOrders();

// 주문 상세
const orderDetail = await client.shop.getOrder(orderId);

// 주문 취소
await client.shop.cancelOrder(orderId);
```

### 결제 (토스페이먼츠)

```typescript
// 결제 준비
const payment = await client.shop.preparePayment({
  order_number: 'ORD-123',
  success_url: 'https://mysite.com/payment/success',
  fail_url: 'https://mysite.com/payment/fail',
});
// payment = { client_key, order_id, order_name, amount, customer_name, ... }

// 결제 승인 (토스 결제 완료 후)
await client.shop.confirmPayment({
  payment_key: 'toss_payment_key',
  order_id: 'ORD-123',
  amount: 50000,
});
```

### 쿠폰

```typescript
// 쿠폰 검증
const result = await client.shop.validateCoupon('SAVE10', 50000);
// { valid: true, discount_amount: 5000, coupon: { ... } }

// 내 쿠폰 목록
const coupons = await client.shop.myCoupons();
```

### 배송 설정

```typescript
const settings = await client.shop.getShippingSettings();
// { base_fee, free_shipping_threshold, ... }
```

---

## Reservation API

### 공개 API

```typescript
// 예약 설정
const settings = await client.reservation.getSettings();

// 서비스 목록
const services = await client.reservation.listServices();

// 담당자 목록
const staff = await client.reservation.listStaff(serviceId);

// 예약 가능 날짜
const dates = await client.reservation.getAvailableDates({
  service_id: 1,
  staff_id: 2,
  start_date: '2026-01-01',
  end_date: '2026-01-31',
});

// 예약 가능 시간
const slots = await client.reservation.getAvailableSlots({
  service_id: 1,
  date: '2026-01-15',
  staff_id: 2,
});
```

### 예약 관리 (인증 필요)

```typescript
// 예약 생성
await client.reservation.create({
  service_id: 1,
  staff_id: 2,
  reservation_date: '2026-01-15',
  start_time: '14:00',
  customer_name: 'John Doe',
  customer_phone: '010-1234-5678',
  customer_email: 'john@example.com',
  notes: '요청사항',
});

// 내 예약 목록
const { data } = await client.reservation.list({ status: 'confirmed' });

// 다가오는/지난 예약
const upcoming = await client.reservation.upcoming(5);
const past = await client.reservation.past(10);

// 예약 취소
await client.reservation.cancel('RES-20260115-001', '일정 변경');
```

---

## Forms API

```typescript
// 폼 목록
const { data: forms } = await client.forms.list();

// 폼 상세 (필드 정보 포함)
const form = await client.forms.get('contact');

// 폼 제출
await client.forms.submit('contact', {
  name: 'John Doe',
  email: 'john@example.com',
  message: 'Hello!',
});

// 내 제출 목록 (인증 필요)
const { data: submissions } = await client.forms.mySubmissions();
```

---

## Media API (인증 필요)

```typescript
// 파일 업로드
const media = await client.media.upload(file);

// 여러 파일 업로드
const mediaList = await client.media.uploadMultiple([file1, file2]);

// 내 미디어 목록
const { data: myMedia } = await client.media.list({ type: 'image/jpeg' });

// 미디어 삭제
await client.media.delete(mediaId);
```

---

## Custom Entities API

동적 데이터 구조 생성/관리

### 엔티티 정의

```typescript
// 엔티티 목록
const entities = await client.entities.list();

// 엔티티 조회
const entity = await client.entities.get('customers');

// 엔티티 생성
await client.entities.create({
  name: 'Customer',
  slug: 'customers',
  schema: {
    fields: [
      { name: 'company', label: 'Company', type: 'text', required: true },
      { name: 'email', label: 'Email', type: 'email', required: true },
      { name: 'status', label: 'Status', type: 'select', options: [
        { value: 'active', label: 'Active' },
        { value: 'inactive', label: 'Inactive' },
      ]},
    ],
  },
});

// 엔티티 수정/삭제
await client.entities.update('customers', { name: 'Clients' });
await client.entities.delete('customers', true);  // force
```

### 레코드

```typescript
// 레코드 목록
const { data: records } = await client.entities.listRecords('customers', {
  search: 'ACME',
  filters: JSON.stringify({ status: 'active' }),
});

// 레코드 조회
const record = await client.entities.getRecord('customers', 1);

// 레코드 생성
await client.entities.createRecord('customers', {
  company: 'ACME Corp',
  email: 'contact@acme.com',
  status: 'active',
});

// 레코드 수정/삭제
await client.entities.updateRecord('customers', 1, { status: 'inactive' });
await client.entities.deleteRecord('customers', 1);
```

### TypeScript 지원

```typescript
interface Customer {
  company: string;
  email: string;
  status: 'active' | 'inactive';
}

const customers = client.entities.typed<Customer>('customers');
const { data } = await customers.list();  // data: Customer[]
```

---

## 응답 타입

모든 목록 API는 일관된 형식 반환:

```typescript
interface ListResponse<T> {
  data: T[];  // 항상 배열, null 아님
  meta: {
    current_page: number;
    last_page: number;
    per_page: number;
    total: number;
    from: number | null;
    to: number | null;
  };
}
```

---

## 에러 처리

```typescript
import { Promptly, PromptlyError } from '@back23/promptly-sdk';

try {
  await client.auth.login({ email: 'wrong@email.com', password: 'wrong' });
} catch (error) {
  if (error instanceof PromptlyError) {
    console.log(error.message);  // "Invalid credentials"
    console.log(error.status);   // 401
    console.log(error.errors);   // { email: ["Invalid email or password"] }
  }
}
```

---

## React 예시

```tsx
'use client';

import { useState, useEffect } from 'react';
import { Promptly } from '@back23/promptly-sdk';

// 싱글톤 클라이언트
const client = new Promptly({
  tenantId: process.env.NEXT_PUBLIC_PROMPTLY_TENANT_ID!,
  apiKey: process.env.NEXT_PUBLIC_PROMPTLY_API_KEY!,
  persistToken: true,
});

function BlogList() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    client.blog.list({ per_page: 10 })
      .then(({ data }) => setPosts(data))
      .finally(() => setLoading(false));
  }, []);

  if (loading) return <div>Loading...</div>;

  return (
    <div>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  );
}
```

---

## Changelog

### v2.18.0
- 상품 리뷰 API 추가
- 배송 설정 API 추가

### v2.15.0
- 토스페이먼츠 결제 API 추가

### v2.12.0
- 블로그 카테고리/태그 필터 추가
- `category`, `tags`, `views`, `published_at` 필드 추가

### v2.10.0
- `persistToken` 옵션 추가 (토큰 자동 저장)
- `onAuthStateChange` 콜백 추가
- `storageType` 옵션 추가

### v2.5.0
- 게시판 비밀글 지원 (`is_secret`, `is_mine`)

### v2.3.0
- 다형성 댓글 API (board, blog, standalone)

### v2.0.0
- **Breaking:** API key 필수

### v1.3.0
- `ListResponse<T>` 통일
- 예약 시스템 지원

---

## 라이선스

MIT