@back23/promptly-sdk 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +708 -0
- package/dist/index.d.mts +1221 -0
- package/dist/index.d.ts +1221 -0
- package/dist/index.js +942 -0
- package/dist/index.mjs +914 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,708 @@
|
|
|
1
|
+
# @webbyon/promptly-sdk
|
|
2
|
+
|
|
3
|
+
Promptly AI CMS SDK for JavaScript/TypeScript
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @webbyon/promptly-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Promptly } from '@webbyon/promptly-sdk';
|
|
15
|
+
|
|
16
|
+
const client = new Promptly({
|
|
17
|
+
tenantId: 'demo',
|
|
18
|
+
baseUrl: 'https://promptly.webbyon.com',
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## API Overview
|
|
23
|
+
|
|
24
|
+
| Resource | Public (No Auth) | Protected (Auth Required) |
|
|
25
|
+
|----------|------------------|---------------------------|
|
|
26
|
+
| **Boards** | list, get | - |
|
|
27
|
+
| **Posts** | listPosts, getPost | createPost, updatePost, deletePost |
|
|
28
|
+
| **Comments** | listComments | createComment, updateComment, deleteComment |
|
|
29
|
+
| **Blog** | list, get | - |
|
|
30
|
+
| **Shop** | listProducts, getProduct, listCategories | getCart, addToCart, listOrders, createOrder |
|
|
31
|
+
| **Forms** | list, get, submit | mySubmissions |
|
|
32
|
+
| **Auth** | login, register | logout, me, updateProfile |
|
|
33
|
+
| **Media** | - | upload, list, delete |
|
|
34
|
+
| **Entities** | list, getSchema, listRecords, getRecord | createRecord, updateRecord, deleteRecord |
|
|
35
|
+
|
|
36
|
+
## API Reference
|
|
37
|
+
|
|
38
|
+
### Boards (게시판) - Public
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// 게시판 목록
|
|
42
|
+
const boards = await client.boards.list();
|
|
43
|
+
// Returns: Board[]
|
|
44
|
+
|
|
45
|
+
// 게시판 상세
|
|
46
|
+
const board = await client.boards.get('first'); // slug or id
|
|
47
|
+
// Returns: Board
|
|
48
|
+
|
|
49
|
+
// 게시판 글 목록
|
|
50
|
+
const posts = await client.boards.listPosts('first', {
|
|
51
|
+
page: 1,
|
|
52
|
+
per_page: 10,
|
|
53
|
+
search: '검색어', // optional
|
|
54
|
+
});
|
|
55
|
+
// Returns: { data: BoardPost[], meta: { current_page, last_page, per_page, total } }
|
|
56
|
+
|
|
57
|
+
// 글 상세
|
|
58
|
+
const post = await client.boards.getPost(1);
|
|
59
|
+
// Returns: BoardPost
|
|
60
|
+
|
|
61
|
+
// 댓글 목록
|
|
62
|
+
const comments = await client.boards.listComments(1);
|
|
63
|
+
// Returns: BoardComment[]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Posts & Comments - Protected (로그인 필요)
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// 먼저 로그인
|
|
70
|
+
await client.auth.login({ email: 'user@example.com', password: 'password' });
|
|
71
|
+
|
|
72
|
+
// 글 작성
|
|
73
|
+
const newPost = await client.boards.createPost({
|
|
74
|
+
board_id: 1,
|
|
75
|
+
title: '제목',
|
|
76
|
+
content: '내용',
|
|
77
|
+
is_notice: false,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// 글 수정
|
|
81
|
+
await client.boards.updatePost(postId, {
|
|
82
|
+
title: '수정된 제목',
|
|
83
|
+
content: '수정된 내용',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// 글 삭제
|
|
87
|
+
await client.boards.deletePost(postId);
|
|
88
|
+
|
|
89
|
+
// 댓글 작성
|
|
90
|
+
await client.boards.createComment(postId, {
|
|
91
|
+
content: '댓글 내용',
|
|
92
|
+
parent_id: null, // 대댓글이면 부모 댓글 ID
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// 댓글 수정
|
|
96
|
+
await client.boards.updateComment(commentId, {
|
|
97
|
+
content: '수정된 댓글',
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// 댓글 삭제
|
|
101
|
+
await client.boards.deleteComment(commentId);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Blog (블로그) - Public
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// 블로그 글 목록
|
|
108
|
+
const posts = await client.blog.list({
|
|
109
|
+
page: 1,
|
|
110
|
+
per_page: 10,
|
|
111
|
+
category: 'news', // optional
|
|
112
|
+
tag: 'featured', // optional
|
|
113
|
+
search: '검색어', // optional
|
|
114
|
+
});
|
|
115
|
+
// Returns: { data: BlogPost[], meta: {...} }
|
|
116
|
+
|
|
117
|
+
// 블로그 글 상세
|
|
118
|
+
const post = await client.blog.get('post-slug');
|
|
119
|
+
// Returns: BlogPost
|
|
120
|
+
|
|
121
|
+
// 추천 글
|
|
122
|
+
const featured = await client.blog.featured(5);
|
|
123
|
+
// Returns: BlogPost[]
|
|
124
|
+
|
|
125
|
+
// 카테고리별 조회
|
|
126
|
+
const newsPosts = await client.blog.byCategory('news');
|
|
127
|
+
|
|
128
|
+
// 태그별 조회
|
|
129
|
+
const taggedPosts = await client.blog.byTag('featured');
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Shop (쇼핑)
|
|
133
|
+
|
|
134
|
+
#### Public (로그인 불필요)
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
// 상품 목록
|
|
138
|
+
const products = await client.shop.listProducts({
|
|
139
|
+
page: 1,
|
|
140
|
+
per_page: 10,
|
|
141
|
+
category: 'electronics', // optional
|
|
142
|
+
is_featured: true, // optional
|
|
143
|
+
search: '검색어', // optional
|
|
144
|
+
});
|
|
145
|
+
// Returns: { data: Product[], meta: {...} }
|
|
146
|
+
|
|
147
|
+
// 상품 상세
|
|
148
|
+
const product = await client.shop.getProduct('product-slug');
|
|
149
|
+
// Returns: Product
|
|
150
|
+
|
|
151
|
+
// 추천 상품
|
|
152
|
+
const featured = await client.shop.featuredProducts(8);
|
|
153
|
+
// Returns: Product[]
|
|
154
|
+
|
|
155
|
+
// 카테고리 목록
|
|
156
|
+
const categories = await client.shop.listCategories();
|
|
157
|
+
// Returns: ProductCategory[]
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### Protected (로그인 필요)
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// 장바구니 조회
|
|
164
|
+
const cart = await client.shop.getCart();
|
|
165
|
+
// Returns: Cart
|
|
166
|
+
|
|
167
|
+
// 장바구니 추가
|
|
168
|
+
await client.shop.addToCart({
|
|
169
|
+
product_id: 1,
|
|
170
|
+
quantity: 2,
|
|
171
|
+
variant_id: 10, // optional - 옵션상품인 경우
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// 장바구니 수량 변경
|
|
175
|
+
await client.shop.updateCartItem(itemId, { quantity: 3 });
|
|
176
|
+
|
|
177
|
+
// 장바구니 삭제
|
|
178
|
+
await client.shop.removeFromCart(itemId);
|
|
179
|
+
|
|
180
|
+
// 장바구니 비우기
|
|
181
|
+
await client.shop.clearCart();
|
|
182
|
+
|
|
183
|
+
// 주문 생성
|
|
184
|
+
const order = await client.shop.createOrder({
|
|
185
|
+
orderer_name: '홍길동',
|
|
186
|
+
orderer_email: 'hong@example.com',
|
|
187
|
+
orderer_phone: '010-1234-5678',
|
|
188
|
+
shipping_name: '홍길동',
|
|
189
|
+
shipping_phone: '010-1234-5678',
|
|
190
|
+
shipping_zipcode: '12345',
|
|
191
|
+
shipping_address: '서울시 강남구',
|
|
192
|
+
shipping_address_detail: '101호',
|
|
193
|
+
shipping_memo: '문 앞에 놓아주세요',
|
|
194
|
+
coupon_code: 'SAVE10', // optional
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// 주문 목록
|
|
198
|
+
const orders = await client.shop.listOrders();
|
|
199
|
+
// Returns: { data: Order[], meta: {...} }
|
|
200
|
+
|
|
201
|
+
// 주문 상세
|
|
202
|
+
const order = await client.shop.getOrder(orderId);
|
|
203
|
+
// Returns: Order
|
|
204
|
+
|
|
205
|
+
// 주문 취소
|
|
206
|
+
await client.shop.cancelOrder(orderId);
|
|
207
|
+
|
|
208
|
+
// 쿠폰 검증
|
|
209
|
+
const validation = await client.shop.validateCoupon('SAVE10', 50000);
|
|
210
|
+
// Returns: { valid: boolean, discount_amount: number, coupon: Coupon }
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Auth (인증)
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// 로그인
|
|
217
|
+
const response = await client.auth.login({
|
|
218
|
+
email: 'user@example.com',
|
|
219
|
+
password: 'password',
|
|
220
|
+
});
|
|
221
|
+
// Returns: { member: Member, token: string }
|
|
222
|
+
// 토큰은 자동으로 저장됨
|
|
223
|
+
|
|
224
|
+
// 회원가입
|
|
225
|
+
await client.auth.register({
|
|
226
|
+
name: '홍길동',
|
|
227
|
+
email: 'user@example.com',
|
|
228
|
+
password: 'password',
|
|
229
|
+
password_confirmation: 'password',
|
|
230
|
+
phone: '010-1234-5678', // optional
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// 로그아웃
|
|
234
|
+
await client.auth.logout();
|
|
235
|
+
|
|
236
|
+
// 내 정보 조회
|
|
237
|
+
const me = await client.auth.me();
|
|
238
|
+
// Returns: Member
|
|
239
|
+
|
|
240
|
+
// 프로필 수정
|
|
241
|
+
await client.auth.updateProfile({
|
|
242
|
+
name: '새이름',
|
|
243
|
+
phone: '010-9999-8888',
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// 비밀번호 변경
|
|
247
|
+
await client.auth.updateProfile({
|
|
248
|
+
current_password: '현재비밀번호',
|
|
249
|
+
password: '새비밀번호',
|
|
250
|
+
password_confirmation: '새비밀번호',
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// 인증 여부 확인
|
|
254
|
+
client.isAuthenticated(); // true or false
|
|
255
|
+
|
|
256
|
+
// 토큰 직접 설정 (localStorage에서 복원시)
|
|
257
|
+
client.setToken('saved-token');
|
|
258
|
+
|
|
259
|
+
// 토큰 가져오기
|
|
260
|
+
const token = client.getToken();
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
#### 소셜 로그인
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
// 소셜 로그인 제공자 목록
|
|
267
|
+
const providers = await client.auth.getSocialProviders();
|
|
268
|
+
// Returns: SocialProvider[]
|
|
269
|
+
|
|
270
|
+
// 소셜 로그인 URL 가져오기
|
|
271
|
+
const { url } = await client.auth.getSocialAuthUrl('google');
|
|
272
|
+
// 해당 URL로 리다이렉트
|
|
273
|
+
|
|
274
|
+
// 콜백 처리 (리다이렉트 후)
|
|
275
|
+
const response = await client.auth.socialCallback('google', code);
|
|
276
|
+
// Returns: { member: Member, token: string }
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Forms (폼) - Public
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
// 폼 목록
|
|
283
|
+
const forms = await client.forms.list();
|
|
284
|
+
// Returns: Form[]
|
|
285
|
+
|
|
286
|
+
// 폼 상세
|
|
287
|
+
const form = await client.forms.get('contact');
|
|
288
|
+
// Returns: Form (필드 정보 포함)
|
|
289
|
+
|
|
290
|
+
// 폼 제출 (로그인 불필요)
|
|
291
|
+
await client.forms.submit('contact', {
|
|
292
|
+
name: '홍길동',
|
|
293
|
+
email: 'user@example.com',
|
|
294
|
+
message: '문의 내용',
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Media (미디어) - Protected
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// 파일 업로드
|
|
302
|
+
const media = await client.media.upload(file); // File or Blob
|
|
303
|
+
// Returns: Media
|
|
304
|
+
|
|
305
|
+
// 여러 파일 업로드
|
|
306
|
+
const mediaList = await client.media.uploadMultiple([file1, file2]);
|
|
307
|
+
// Returns: Media[]
|
|
308
|
+
|
|
309
|
+
// 내 미디어 목록
|
|
310
|
+
const mediaList = await client.media.list({
|
|
311
|
+
page: 1,
|
|
312
|
+
per_page: 20,
|
|
313
|
+
type: 'image/jpeg', // optional
|
|
314
|
+
});
|
|
315
|
+
// Returns: { data: Media[], meta: {...} }
|
|
316
|
+
|
|
317
|
+
// 미디어 삭제
|
|
318
|
+
await client.media.delete(mediaId);
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Entities (커스텀 엔티티) - AI가 생성한 동적 데이터
|
|
322
|
+
|
|
323
|
+
AI가 MCP를 통해 생성한 커스텀 데이터 구조에 접근합니다.
|
|
324
|
+
|
|
325
|
+
#### Public
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
// 엔티티 목록 조회
|
|
329
|
+
const entities = await client.entities.list();
|
|
330
|
+
// Returns: CustomEntity[]
|
|
331
|
+
|
|
332
|
+
// 엔티티 스키마 조회
|
|
333
|
+
const schema = await client.entities.getSchema('customer');
|
|
334
|
+
// Returns: EntitySchema
|
|
335
|
+
|
|
336
|
+
// 레코드 목록 조회
|
|
337
|
+
const customers = await client.entities.listRecords('customer', {
|
|
338
|
+
page: 1,
|
|
339
|
+
per_page: 20,
|
|
340
|
+
status: 'active',
|
|
341
|
+
});
|
|
342
|
+
// Returns: { data: EntityRecord[], meta: {...} }
|
|
343
|
+
|
|
344
|
+
// 데이터 필드로 필터링
|
|
345
|
+
const vipCustomers = await client.entities.listRecords('customer', {
|
|
346
|
+
'data.tier': 'vip',
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// 단일 레코드 조회
|
|
350
|
+
const customer = await client.entities.getRecord('customer', 1);
|
|
351
|
+
// Returns: EntityRecord
|
|
352
|
+
console.log(customer.data.company); // 'ABC Corp'
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
#### Protected (로그인 필요)
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
// 레코드 생성
|
|
359
|
+
const newCustomer = await client.entities.createRecord('customer', {
|
|
360
|
+
data: {
|
|
361
|
+
company: 'ABC Corp',
|
|
362
|
+
email: 'contact@abc.com',
|
|
363
|
+
tier: 'standard',
|
|
364
|
+
},
|
|
365
|
+
status: 'active',
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// 레코드 수정
|
|
369
|
+
await client.entities.updateRecord('customer', 1, {
|
|
370
|
+
data: { tier: 'vip' },
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// 레코드 삭제
|
|
374
|
+
await client.entities.deleteRecord('customer', 1);
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
#### TypeScript 타입 지원
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
// 타입이 지정된 엔티티 접근자
|
|
381
|
+
interface Customer {
|
|
382
|
+
company: string;
|
|
383
|
+
email: string;
|
|
384
|
+
tier: 'standard' | 'vip';
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const customers = client.entities.typed<Customer>('customer');
|
|
388
|
+
|
|
389
|
+
// 타입이 추론됨
|
|
390
|
+
const list = await customers.list();
|
|
391
|
+
list.data[0].data.company; // string
|
|
392
|
+
|
|
393
|
+
const record = await customers.get(1);
|
|
394
|
+
record.data.tier; // 'standard' | 'vip'
|
|
395
|
+
|
|
396
|
+
// 생성/수정도 타입 체크
|
|
397
|
+
await customers.create({
|
|
398
|
+
company: 'New Corp',
|
|
399
|
+
email: 'new@corp.com',
|
|
400
|
+
tier: 'standard',
|
|
401
|
+
});
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Site Settings - Public
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
// 테마 설정
|
|
408
|
+
const theme = await client.getTheme();
|
|
409
|
+
// Returns: { name, colors, fonts }
|
|
410
|
+
|
|
411
|
+
// 사이트 설정
|
|
412
|
+
const settings = await client.getSettings();
|
|
413
|
+
// Returns: Record<string, any>
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## Types
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
interface Board {
|
|
420
|
+
id: number;
|
|
421
|
+
slug: string;
|
|
422
|
+
name: string;
|
|
423
|
+
description?: string;
|
|
424
|
+
is_active: boolean;
|
|
425
|
+
created_at: string;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
interface BoardPost {
|
|
429
|
+
id: number;
|
|
430
|
+
board_id: number;
|
|
431
|
+
title: string;
|
|
432
|
+
content: string;
|
|
433
|
+
excerpt?: string;
|
|
434
|
+
author: string;
|
|
435
|
+
views: number;
|
|
436
|
+
is_notice: boolean;
|
|
437
|
+
is_private: boolean;
|
|
438
|
+
comment_count: number;
|
|
439
|
+
attachments?: Media[];
|
|
440
|
+
created_at: string;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
interface BoardComment {
|
|
444
|
+
id: number;
|
|
445
|
+
post_id: number;
|
|
446
|
+
member?: Member;
|
|
447
|
+
parent_id?: number;
|
|
448
|
+
content: string;
|
|
449
|
+
replies?: BoardComment[];
|
|
450
|
+
created_at: string;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
interface BlogPost {
|
|
454
|
+
id: number;
|
|
455
|
+
slug: string;
|
|
456
|
+
title: string;
|
|
457
|
+
content: string;
|
|
458
|
+
excerpt?: string;
|
|
459
|
+
featured_image?: string;
|
|
460
|
+
category?: string;
|
|
461
|
+
tags?: string[];
|
|
462
|
+
author_name?: string;
|
|
463
|
+
is_published: boolean;
|
|
464
|
+
published_at?: string;
|
|
465
|
+
view_count: number;
|
|
466
|
+
seo_title?: string;
|
|
467
|
+
seo_description?: string;
|
|
468
|
+
created_at: string;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
interface Product {
|
|
472
|
+
id: number;
|
|
473
|
+
slug: string;
|
|
474
|
+
name: string;
|
|
475
|
+
description?: string;
|
|
476
|
+
content?: string;
|
|
477
|
+
price: number;
|
|
478
|
+
compare_price?: number;
|
|
479
|
+
thumbnail?: string;
|
|
480
|
+
images?: string[];
|
|
481
|
+
status: 'draft' | 'active' | 'inactive';
|
|
482
|
+
is_featured: boolean;
|
|
483
|
+
has_options: boolean;
|
|
484
|
+
variants?: ProductVariant[];
|
|
485
|
+
in_stock?: boolean;
|
|
486
|
+
discount_percent?: number;
|
|
487
|
+
created_at: string;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
interface Cart {
|
|
491
|
+
id: number;
|
|
492
|
+
items: CartItem[];
|
|
493
|
+
total: number;
|
|
494
|
+
total_quantity: number;
|
|
495
|
+
item_count: number;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
interface Order {
|
|
499
|
+
id: number;
|
|
500
|
+
order_number: string;
|
|
501
|
+
status: 'pending' | 'paid' | 'preparing' | 'shipping' | 'delivered' | 'cancelled';
|
|
502
|
+
subtotal: number;
|
|
503
|
+
discount_amount: number;
|
|
504
|
+
shipping_fee: number;
|
|
505
|
+
total: number;
|
|
506
|
+
items?: OrderItem[];
|
|
507
|
+
created_at: string;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
interface Member {
|
|
511
|
+
id: number;
|
|
512
|
+
name: string;
|
|
513
|
+
email: string;
|
|
514
|
+
phone?: string;
|
|
515
|
+
avatar?: string;
|
|
516
|
+
is_active: boolean;
|
|
517
|
+
created_at: string;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
interface Form {
|
|
521
|
+
id: number;
|
|
522
|
+
slug: string;
|
|
523
|
+
name: string;
|
|
524
|
+
description?: string;
|
|
525
|
+
fields: FormField[];
|
|
526
|
+
settings: FormSettings;
|
|
527
|
+
is_active: boolean;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
interface Media {
|
|
531
|
+
id: number;
|
|
532
|
+
url: string;
|
|
533
|
+
thumbnail_url?: string;
|
|
534
|
+
filename: string;
|
|
535
|
+
mime_type: string;
|
|
536
|
+
size: number;
|
|
537
|
+
created_at: string;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
interface CustomEntity {
|
|
541
|
+
id: number;
|
|
542
|
+
name: string;
|
|
543
|
+
slug: string;
|
|
544
|
+
description?: string;
|
|
545
|
+
schema: EntitySchema;
|
|
546
|
+
icon?: string;
|
|
547
|
+
is_active: boolean;
|
|
548
|
+
records_count?: number;
|
|
549
|
+
created_at: string;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
interface EntitySchema {
|
|
553
|
+
fields: EntityField[];
|
|
554
|
+
display?: {
|
|
555
|
+
title_field?: string;
|
|
556
|
+
list_fields?: string;
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
interface EntityField {
|
|
561
|
+
name: string;
|
|
562
|
+
label: string;
|
|
563
|
+
type: 'text' | 'textarea' | 'number' | 'email' | 'url' | 'date' | 'datetime' | 'boolean' | 'select' | 'multiselect';
|
|
564
|
+
required?: boolean;
|
|
565
|
+
searchable?: boolean;
|
|
566
|
+
default?: any;
|
|
567
|
+
options?: Array<{ value: string; label: string }>;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
interface EntityRecord {
|
|
571
|
+
id: number;
|
|
572
|
+
entity_id: number;
|
|
573
|
+
data: Record<string, any>;
|
|
574
|
+
status: 'active' | 'archived' | 'draft';
|
|
575
|
+
created_at: string;
|
|
576
|
+
updated_at: string;
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
## Error Handling
|
|
581
|
+
|
|
582
|
+
```typescript
|
|
583
|
+
import { Promptly, PromptlyError } from '@webbyon/promptly-sdk';
|
|
584
|
+
|
|
585
|
+
try {
|
|
586
|
+
await client.auth.login({ email: 'wrong@email.com', password: 'wrong' });
|
|
587
|
+
} catch (error) {
|
|
588
|
+
if (error instanceof PromptlyError) {
|
|
589
|
+
console.log(error.message); // "Invalid credentials"
|
|
590
|
+
console.log(error.status); // 401
|
|
591
|
+
console.log(error.errors); // { email: ["Invalid email or password"] }
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
## React Example
|
|
597
|
+
|
|
598
|
+
```tsx
|
|
599
|
+
import { useState, useEffect } from 'react';
|
|
600
|
+
import { Promptly } from '@webbyon/promptly-sdk';
|
|
601
|
+
|
|
602
|
+
const client = new Promptly({
|
|
603
|
+
tenantId: 'demo',
|
|
604
|
+
baseUrl: 'https://promptly.webbyon.com',
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
// 게시판 글 목록
|
|
608
|
+
function BoardPosts({ boardSlug }) {
|
|
609
|
+
const [posts, setPosts] = useState([]);
|
|
610
|
+
const [loading, setLoading] = useState(true);
|
|
611
|
+
|
|
612
|
+
useEffect(() => {
|
|
613
|
+
client.boards.listPosts(boardSlug)
|
|
614
|
+
.then(res => setPosts(res.data))
|
|
615
|
+
.finally(() => setLoading(false));
|
|
616
|
+
}, [boardSlug]);
|
|
617
|
+
|
|
618
|
+
if (loading) return <div>Loading...</div>;
|
|
619
|
+
|
|
620
|
+
return (
|
|
621
|
+
<table>
|
|
622
|
+
<thead>
|
|
623
|
+
<tr>
|
|
624
|
+
<th>제목</th>
|
|
625
|
+
<th>작성자</th>
|
|
626
|
+
<th>조회수</th>
|
|
627
|
+
<th>작성일</th>
|
|
628
|
+
</tr>
|
|
629
|
+
</thead>
|
|
630
|
+
<tbody>
|
|
631
|
+
{posts.map(post => (
|
|
632
|
+
<tr key={post.id}>
|
|
633
|
+
<td>{post.title}</td>
|
|
634
|
+
<td>{post.author}</td>
|
|
635
|
+
<td>{post.views}</td>
|
|
636
|
+
<td>{new Date(post.created_at).toLocaleDateString()}</td>
|
|
637
|
+
</tr>
|
|
638
|
+
))}
|
|
639
|
+
</tbody>
|
|
640
|
+
</table>
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// 로그인 폼
|
|
645
|
+
function LoginForm() {
|
|
646
|
+
const [email, setEmail] = useState('');
|
|
647
|
+
const [password, setPassword] = useState('');
|
|
648
|
+
const [error, setError] = useState('');
|
|
649
|
+
|
|
650
|
+
const handleSubmit = async (e) => {
|
|
651
|
+
e.preventDefault();
|
|
652
|
+
try {
|
|
653
|
+
await client.auth.login({ email, password });
|
|
654
|
+
// 로그인 성공 - 리다이렉트 등
|
|
655
|
+
} catch (err) {
|
|
656
|
+
setError(err.message);
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
return (
|
|
661
|
+
<form onSubmit={handleSubmit}>
|
|
662
|
+
<input
|
|
663
|
+
type="email"
|
|
664
|
+
value={email}
|
|
665
|
+
onChange={e => setEmail(e.target.value)}
|
|
666
|
+
placeholder="이메일"
|
|
667
|
+
/>
|
|
668
|
+
<input
|
|
669
|
+
type="password"
|
|
670
|
+
value={password}
|
|
671
|
+
onChange={e => setPassword(e.target.value)}
|
|
672
|
+
placeholder="비밀번호"
|
|
673
|
+
/>
|
|
674
|
+
{error && <p style={{color: 'red'}}>{error}</p>}
|
|
675
|
+
<button type="submit">로그인</button>
|
|
676
|
+
</form>
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// 상품 목록
|
|
681
|
+
function ProductList() {
|
|
682
|
+
const [products, setProducts] = useState([]);
|
|
683
|
+
|
|
684
|
+
useEffect(() => {
|
|
685
|
+
client.shop.listProducts({ is_featured: true })
|
|
686
|
+
.then(res => setProducts(res.data));
|
|
687
|
+
}, []);
|
|
688
|
+
|
|
689
|
+
return (
|
|
690
|
+
<div className="grid grid-cols-4 gap-4">
|
|
691
|
+
{products.map(product => (
|
|
692
|
+
<div key={product.id} className="border p-4">
|
|
693
|
+
<img src={product.thumbnail} alt={product.name} />
|
|
694
|
+
<h3>{product.name}</h3>
|
|
695
|
+
<p>{product.price.toLocaleString()}원</p>
|
|
696
|
+
{product.compare_price && (
|
|
697
|
+
<p className="line-through">{product.compare_price.toLocaleString()}원</p>
|
|
698
|
+
)}
|
|
699
|
+
</div>
|
|
700
|
+
))}
|
|
701
|
+
</div>
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
## License
|
|
707
|
+
|
|
708
|
+
MIT
|