@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 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