create-entity-server 0.0.9 → 0.0.15

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.
Files changed (46) hide show
  1. package/bin/create.js +11 -1
  2. package/package.json +1 -1
  3. package/template/.env.example +17 -1
  4. package/template/configs/jwt.json +1 -0
  5. package/template/configs/oauth.json +37 -0
  6. package/template/configs/push.json +26 -0
  7. package/template/entities/README.md +4 -4
  8. package/template/entities/{Auth → System/Auth}/account.json +0 -14
  9. package/template/samples/README.md +16 -0
  10. package/template/samples/entities/01_basic_fields.json +39 -0
  11. package/template/samples/entities/02_types_and_defaults.json +68 -0
  12. package/template/samples/entities/03_hash_and_unique.json +33 -0
  13. package/template/samples/entities/04_fk_and_composite_unique.json +31 -0
  14. package/template/samples/entities/05_cache.json +54 -0
  15. package/template/samples/entities/06_history_and_hard_delete.json +42 -0
  16. package/template/samples/entities/07_license_scope.json +43 -0
  17. package/template/samples/entities/08_hook_sql.json +52 -0
  18. package/template/samples/entities/09_hook_entity.json +71 -0
  19. package/template/samples/entities/10_hook_submit_delete.json +75 -0
  20. package/template/samples/entities/11_hook_webhook.json +82 -0
  21. package/template/samples/entities/12_hook_push.json +73 -0
  22. package/template/samples/entities/13_read_only.json +51 -0
  23. package/template/samples/entities/14_optimistic_lock.json +29 -0
  24. package/template/samples/entities/15_reset_defaults.json +95 -0
  25. package/template/samples/entities/README.md +94 -0
  26. package/template/samples/entities/order_notification.json +51 -0
  27. package/template/samples/flutter/lib/entity_server_client.dart +91 -0
  28. package/template/samples/java/EntityServerClient.java +117 -0
  29. package/template/samples/kotlin/EntityServerClient.kt +86 -0
  30. package/template/samples/node/src/EntityServerClient.js +116 -0
  31. package/template/samples/php/ci4/Config/EntityServer.php +15 -0
  32. package/template/samples/php/ci4/Controllers/EntityController.php +202 -0
  33. package/template/samples/php/ci4/Controllers/ProductController.php +16 -76
  34. package/template/samples/php/ci4/Libraries/EntityServer.php +150 -11
  35. package/template/samples/php/laravel/Services/EntityServerService.php +56 -0
  36. package/template/samples/python/entity_server.py +106 -0
  37. package/template/samples/react/src/api/entityServerClient.ts +123 -0
  38. package/template/samples/react/src/hooks/useEntity.ts +68 -0
  39. package/template/samples/swift/EntityServerClient.swift +105 -0
  40. package/template/scripts/normalize-entities.sh +10 -10
  41. package/template/scripts/run.sh +108 -29
  42. package/template/scripts/update-server.ps1 +92 -2
  43. package/template/scripts/update-server.sh +73 -2
  44. /package/template/entities/{Auth → System/Auth}/api_keys.json +0 -0
  45. /package/template/entities/{Auth → System/Auth}/license.json +0 -0
  46. /package/template/entities/{Auth → System/Auth}/rbac_roles.json +0 -0
package/bin/create.js CHANGED
@@ -193,6 +193,15 @@ async function run() {
193
193
  path.join(targetDir, "entities"),
194
194
  );
195
195
 
196
+ // samples/ (없어도 빈 디렉터리 생성)
197
+ const samplesSrc = path.join(templateDir, "samples");
198
+ const samplesDest = path.join(targetDir, "samples");
199
+ if (fs.existsSync(samplesSrc)) {
200
+ copyDir(samplesSrc, samplesDest);
201
+ } else {
202
+ fs.mkdirSync(samplesDest, { recursive: true });
203
+ }
204
+
196
205
  // .env.example 복사 + .env 생성 (없을 때만)
197
206
  copyFile(
198
207
  path.join(templateDir, ".env.example"),
@@ -260,7 +269,8 @@ async function run() {
260
269
  ├── .env ← 환경 변수 설정 (여기를 먼저 수정하세요)
261
270
  ├── configs/ ← CORS, JWT 등 서버 설정
262
271
  ├── entities/ ← 엔티티 스키마 JSON (샘플 포함)
263
- └── scripts/ ← 운영 스크립트
272
+ ├── scripts/ ← 운영 스크립트
273
+ └── samples/ ← 샘플 파일
264
274
 
265
275
  다음 단계:
266
276
  ${cdCmd ? ` ${cdCmd}` : ""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-entity-server",
3
- "version": "0.0.9",
3
+ "version": "0.0.15",
4
4
  "description": "Create a new entity-server project in one command — like create-react-app or create-vite.",
5
5
  "keywords": [
6
6
  "entity-server",
@@ -9,6 +9,22 @@ ENCRYPTION_KEY=your-32-char-hex-encryption-key-here
9
9
  # 운영에서는 충분히 긴 랜덤 시크릿을 사용하세요.
10
10
  JWT_SECRET=your-jwt-secret-here
11
11
 
12
- # Database passwords (database.json의 password_env와 매핑)
12
+ # 서버 포트 (configs/server.json의 port를 오버라이드)
13
+ SERVER_PORT=47200
14
+
15
+ # Database values (database.json의 ${ENV_VAR}와 매핑)
16
+ DB_NAME_DEVELOPMENT=your-development-db-name
17
+ DB_USER_DEVELOPMENT=your-development-db-user
13
18
  DB_PASSWORD_DEVELOPMENT=your-development-db-password
19
+
20
+ DB_NAME_PRODUCTION=your-production-db-name
21
+ DB_USER_PRODUCTION=your-production-db-user
14
22
  DB_PASSWORD_PRODUCTION=your-production-db-password
23
+
24
+ # PostgreSQL group (선택)
25
+ # DB_NAME_POSTGRESQL=your-postgresql-db-name
26
+ # DB_USER_POSTGRESQL=your-postgresql-db-user
27
+ # DB_PASSWORD_POSTGRESQL=your-postgresql-db-password
28
+
29
+ # 푸시 알림 (push.json에서 ${FCM_PROJECT_ID} 등으로 참조 가능)
30
+ # FCM_PROJECT_ID=your-firebase-project-id
@@ -1,4 +1,5 @@
1
1
  {
2
+ "enabled": true,
2
3
  "secret": "${JWT_SECRET}",
3
4
  "access_ttl_sec": 3600,
4
5
  "refresh_ttl_sec": 1209600,
@@ -0,0 +1,37 @@
1
+ {
2
+ "_comment": "OAuth 2.0 설정 예시. 사용할 프로바이더만 남기고 실제 값으로 교체하세요.",
3
+
4
+ "state_secret": "${OAUTH_STATE_SECRET}",
5
+ "state_ttl_sec": 600,
6
+
7
+ "providers": {
8
+ "google": {
9
+ "client_id": "${GOOGLE_CLIENT_ID}",
10
+ "client_secret": "${GOOGLE_CLIENT_SECRET}",
11
+ "redirect_url": "https://your-domain.com/v1/oauth/google/callback",
12
+ "scopes": ["openid", "email", "profile"]
13
+ },
14
+ "github": {
15
+ "client_id": "${GITHUB_CLIENT_ID}",
16
+ "client_secret": "${GITHUB_CLIENT_SECRET}",
17
+ "redirect_url": "https://your-domain.com/v1/oauth/github/callback"
18
+ },
19
+ "naver": {
20
+ "client_id": "${NAVER_CLIENT_ID}",
21
+ "client_secret": "${NAVER_CLIENT_SECRET}",
22
+ "redirect_url": "https://your-domain.com/v1/oauth/naver/callback"
23
+ },
24
+ "kakao": {
25
+ "_comment": "Kakao — 커스텀 엔드포인트 예시",
26
+ "client_id": "${KAKAO_CLIENT_ID}",
27
+ "client_secret": "${KAKAO_CLIENT_SECRET}",
28
+ "redirect_url": "https://your-domain.com/v1/oauth/kakao/callback",
29
+ "auth_url": "https://kauth.kakao.com/oauth/authorize",
30
+ "token_url": "https://kauth.kakao.com/oauth/token",
31
+ "user_info_url": "https://kapi.kakao.com/v2/user/me",
32
+ "scopes": ["profile_nickname", "account_email"],
33
+ "email_field": "kakao_account.email",
34
+ "name_field": "properties.nickname"
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "workers": 2,
3
+ "queue_size": 500,
4
+ "fcm": {
5
+ "enabled": true,
6
+ "type": "service_account",
7
+ "project_id": "your-firebase-project-id",
8
+ "private_key_id": "abc123def456...",
9
+ "private_key_file": "./configs/keys/firebase.pem",
10
+ "client_email": "firebase-adminsdk-xxxxx@your-firebase-project-id.iam.gserviceaccount.com",
11
+ "client_id": "123456789012345678901",
12
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
13
+ "token_uri": "https://oauth2.googleapis.com/token",
14
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
15
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-xxxxx%40your-firebase-project-id.iam.gserviceaccount.com"
16
+ },
17
+ "apns": {
18
+ "enabled": false,
19
+ "_comment": "APNs 직접 전송 (미구현 - 예약)",
20
+ "key_file": "./configs/keys/apns.p8",
21
+ "key_id": "ABCDE12345",
22
+ "team_id": "FGHIJ67890",
23
+ "bundle_id": "com.example.myapp",
24
+ "production": false
25
+ }
26
+ }
@@ -50,11 +50,11 @@
50
50
 
51
51
  ### 3. examples/Auth/account.json
52
52
 
53
- 사용자 관리 엔티티 (`entities/examples/Auth/` 에 위치 → 배포 시 `dist/entities/Auth/account.json`)
53
+ 사용자 관리 엔티티 (`entities/examples/Auth/` 에 위치 → 배포 시 `dist/entities/System/Auth/account.json`)
54
54
 
55
55
  > **⚠️ JWT 인증을 사용하려면 필수입니다.**
56
56
  > HMAC 인증만 사용하는 경우에는 필요하지 않습니다.
57
- > `jwt.json`에 `secret`이 설정된 경우 서버 기동 시 `entities/Auth/account.json`이 존재하지 않으면 오류가 발생합니다.
57
+ > `jwt.json`에 `secret`이 설정된 경우 서버 기동 시 `entities/System/Auth/account.json`이 존재하지 않으면 오류가 발생합니다.
58
58
  > `email`, `rbac_role` 필드가 index에 포함되어 있어야 합니다.
59
59
  > `./scripts/reset-all.sh --apply` 실행 시 파일이 없으면 자동으로 생성됩니다.
60
60
 
@@ -68,7 +68,7 @@
68
68
 
69
69
  ### 4. examples/Auth/rbac_roles.json
70
70
 
71
- RBAC 역할 정의 엔티티 (`entities/examples/Auth/` 에 위치 → 배포 시 `dist/entities/Auth/rbac_roles.json`)
71
+ RBAC 역할 정의 엔티티 (`entities/examples/Auth/` 에 위치 → 배포 시 `dist/entities/System/Auth/rbac_roles.json`)
72
72
 
73
73
  > **⚠️ RBAC 인증을 사용하려면 필수입니다.**
74
74
  > `reset-all` 실행 시 `reset_defaults`의 5개 역할이 자동 시딩됩니다.
@@ -84,7 +84,7 @@ RBAC 역할 정의 엔티티 (`entities/examples/Auth/` 에 위치 → 배포
84
84
 
85
85
  ### 5. examples/Auth/api_keys.json
86
86
 
87
- API 키 관리 엔티티 (`entities/examples/Auth/` 에 위치 → 배포 시 `dist/entities/Auth/api_keys.json`)
87
+ API 키 관리 엔티티 (`entities/examples/Auth/` 에 위치 → 배포 시 `dist/entities/System/Auth/api_keys.json`)
88
88
 
89
89
  > **⚠️ HMAC 인증을 사용하려면 필수입니다.**
90
90
  > `reset-all` 실행 시 admin 역할의 API 키 1개가 자동 생성되며 `key_value`와 `hmac_secret`이 출력됩니다.
@@ -34,20 +34,6 @@
34
34
  }
35
35
  ],
36
36
  "hooks": {
37
- "before_insert": [
38
- {
39
- "type": "submit",
40
- "entity": "user",
41
- "match": {
42
- "email": "${new.email}"
43
- },
44
- "data": {
45
- "email": "${new.email}",
46
- "status": "active"
47
- },
48
- "assign_seq_to": "user_seq"
49
- }
50
- ],
51
37
  "after_insert": [
52
38
  {
53
39
  "type": "sql",
@@ -57,9 +57,25 @@ signature = HMAC-SHA256(hmacSecret, payload) → hex
57
57
 
58
58
  | 디렉토리 | 프레임워크 | 인증 방식 |
59
59
  | -------------- | ---------------------- | --------- |
60
+ | `entities/` | 엔티티 설정 예제 | — |
60
61
  | `php/ci4/` | CodeIgniter 4 | HMAC |
61
62
  | `php/laravel/` | Laravel | HMAC |
62
63
  | `java/` | Java (표준 라이브러리) | HMAC |
63
64
  | `node/` | Node.js (fetch) | HMAC |
64
65
  | `python/` | Python (requests) | HMAC |
65
66
  | `react/` | React + TypeScript | JWT |
67
+
68
+ ### CI4 설정 방식
69
+
70
+ - `php/ci4` 샘플은 `app/Config/EntityServer.php` 중심으로 설정을 관리합니다.
71
+ - 다른 언어 샘플은 각 라이브러리/클라이언트 파일 내부 설정값을 직접 수정해 사용합니다.
72
+
73
+ ### Push 헬퍼
74
+
75
+ - 모든 샘플 클라이언트는 푸시 트리거용 헬퍼를 제공합니다. - `push(pushEntity, payload[, transactionId])` : 내부적으로 `submit(pushEntity, payload)` 호출 - `pushLogList(...)` : 내부적으로 `list('push_log', ...)` 호출
76
+ - Python 샘플은 PEP8 네이밍으로 `push_log_list(...)` 메서드를 제공합니다.
77
+
78
+ ### 요청 본문(암호 패킷) 읽기 헬퍼
79
+
80
+ - 모든 샘플에 요청 본문 파싱 헬퍼를 제공합니다. - `application/octet-stream`이면 XChaCha20-Poly1305 패킷을 복호화해 JSON 반환 - 그 외 Content-Type이면 평문 JSON 파싱 - 옵션으로 `requireEncrypted=true` 정책을 줄 수 있습니다.
81
+ - 언어별 메서드명 - Node/React/Kotlin/Java/Swift/Laravel: `readRequestBody(...)` - Python: `read_request_body(...)` - Flutter: `readRequestBody(...)`
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "contact",
3
+ "description": "기본 필드 타입 예제 — 인덱스 필드 자동 추론 패턴 모음",
4
+ "index": {
5
+ "name": {
6
+ "comment": "이름 (*_name → VARCHAR(100) 자동 추론)"
7
+ },
8
+ "email": {
9
+ "comment": "이메일 (*email* → VARCHAR(255) 자동 추론)",
10
+ "type": "email"
11
+ },
12
+ "phone": {
13
+ "comment": "전화번호 (*phone* → VARCHAR(50) 자동 추론)"
14
+ },
15
+ "birth_date": {
16
+ "comment": "생년월일 (*_date → DATE 자동 추론)"
17
+ },
18
+ "joined_at": {
19
+ "comment": "가입일시 (*_at → DATETIME 자동 추론)"
20
+ },
21
+ "visit_count": {
22
+ "comment": "방문 횟수 (*_count → INT 자동 추론)"
23
+ },
24
+ "total_amount": {
25
+ "comment": "총 금액 (*_amount → DECIMAL(15,2) 자동 추론)"
26
+ },
27
+ "is_verified": {
28
+ "comment": "인증 여부 (is_* → TINYINT(1) 자동 추론)"
29
+ },
30
+ "user_seq": {
31
+ "comment": "연결된 사용자 seq (*_seq → BIGINT UNSIGNED 자동 추론)"
32
+ },
33
+ "status": {
34
+ "comment": "상태 (enum 배열로 허용값 제한)",
35
+ "type": ["active", "inactive"],
36
+ "default": "active"
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "product",
3
+ "description": "명시적 타입 선언 & defaults 예제 — 자동 추론이 안 되는 필드에 types 사용",
4
+ "index": {
5
+ "sku": {
6
+ "comment": "상품 코드",
7
+ "required": true,
8
+ "unique": true
9
+ },
10
+ "category": {
11
+ "comment": "카테고리",
12
+ "type": ["electronics", "furniture", "clothing", "food"],
13
+ "default": "electronics"
14
+ },
15
+ "price": {
16
+ "comment": "정가 (decimal — 자동추론 불가, 명시 선언)",
17
+ "type": "decimal"
18
+ },
19
+ "stock_qty": {
20
+ "comment": "재고 수량 (*_qty → INT 자동 추론)"
21
+ },
22
+ "weight": {
23
+ "comment": "무게(kg) — 자동추론 없음, types에서 decimal 선언",
24
+ "type": "decimal"
25
+ },
26
+ "is_available": {
27
+ "comment": "판매 가능 여부 (is_* → TINYINT(1) 자동 추론)",
28
+ "default": true
29
+ },
30
+ "launched_at": {
31
+ "comment": "출시일시 (*_at → DATETIME 자동 추론)"
32
+ }
33
+ },
34
+ "types": {
35
+ "description": "text",
36
+ "thumbnail_url": "varchar(500)",
37
+ "spec_json": "text",
38
+ "barcode": "varchar(50)"
39
+ },
40
+ "comments": {
41
+ "description": "상품 설명 (긴 텍스트)",
42
+ "thumbnail_url": "썸네일 이미지 URL",
43
+ "spec_json": "상세 스펙 JSON",
44
+ "barcode": "바코드"
45
+ },
46
+ "defaults": {
47
+ "is_available": true,
48
+ "stock_qty": 0
49
+ },
50
+ "reset_defaults": [
51
+ {
52
+ "sku": "ELEC-001",
53
+ "category": "electronics",
54
+ "price": 99000,
55
+ "stock_qty": 100,
56
+ "is_available": true,
57
+ "description": "샘플 전자제품"
58
+ },
59
+ {
60
+ "sku": "FURN-001",
61
+ "category": "furniture",
62
+ "price": 299000,
63
+ "stock_qty": 20,
64
+ "is_available": true,
65
+ "description": "샘플 가구"
66
+ }
67
+ ]
68
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "member",
3
+ "description": "hash & unique 예제 — 민감 필드 해시 저장 + 단일/복합 유니크",
4
+ "index": {
5
+ "email": {
6
+ "comment": "이메일 (고유 — 중복 불가)",
7
+ "type": "email",
8
+ "required": true,
9
+ "unique": true
10
+ },
11
+ "phone": {
12
+ "comment": "전화번호 (해시 저장 — 평문 노출 차단)",
13
+ "hash": true
14
+ },
15
+ "resident_id": {
16
+ "comment": "주민등록번호 앞 7자리 (해시 + 유니크 — 중복 가입 방지)",
17
+ "hash": true,
18
+ "unique": true
19
+ },
20
+ "nickname": {
21
+ "comment": "닉네임 (unique — 중복 불가)"
22
+ },
23
+ "org_seq": {
24
+ "comment": "소속 조직 seq"
25
+ },
26
+ "status": {
27
+ "comment": "상태",
28
+ "type": ["active", "inactive", "withdrawn"],
29
+ "default": "active"
30
+ }
31
+ },
32
+ "unique": [["org_seq", "nickname"]]
33
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "team_member",
3
+ "description": "외래키(fk) & 복합 유니크 예제 — 팀-사용자 다대다 중간 테이블",
4
+ "index": {
5
+ "team_seq": {
6
+ "comment": "팀 seq (fk: team.seq)",
7
+ "required": true
8
+ },
9
+ "user_seq": {
10
+ "comment": "사용자 seq (fk: user.seq)",
11
+ "required": true
12
+ },
13
+ "role": {
14
+ "comment": "팀 내 역할",
15
+ "type": ["owner", "admin", "member", "viewer"],
16
+ "default": "member"
17
+ },
18
+ "invited_by": {
19
+ "comment": "초대한 사용자 seq (fk: user.seq, nullable)"
20
+ },
21
+ "joined_at": {
22
+ "comment": "참가일시 (*_at → DATETIME 자동 추론)"
23
+ }
24
+ },
25
+ "unique": [["team_seq", "user_seq"]],
26
+ "fk": {
27
+ "team_seq": "team.seq",
28
+ "user_seq": "user.seq",
29
+ "invited_by": "user.seq"
30
+ }
31
+ }
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "config_item",
3
+ "description": "캐시 예제 — 자주 읽히고 드물게 변경되는 설정값에 엔티티 레벨 캐시 활성화",
4
+ "index": {
5
+ "key": {
6
+ "comment": "설정 키 (고유)",
7
+ "required": true,
8
+ "unique": true
9
+ },
10
+ "category": {
11
+ "comment": "설정 카테고리",
12
+ "type": ["system", "ui", "feature_flag", "notification"],
13
+ "default": "system"
14
+ },
15
+ "is_public": {
16
+ "comment": "공개 여부 (is_* → TINYINT(1) 자동 추론)"
17
+ }
18
+ },
19
+ "types": {
20
+ "value": "text",
21
+ "description": "text"
22
+ },
23
+ "comments": {
24
+ "value": "설정 값 (JSON 또는 문자열)",
25
+ "description": "설정 설명"
26
+ },
27
+ "cache": {
28
+ "enabled": true,
29
+ "ttl_seconds": 600
30
+ },
31
+ "reset_defaults": [
32
+ {
33
+ "key": "site.name",
34
+ "category": "system",
35
+ "value": "My Service",
36
+ "is_public": true,
37
+ "description": "서비스명"
38
+ },
39
+ {
40
+ "key": "feature.dark_mode",
41
+ "category": "feature_flag",
42
+ "value": "true",
43
+ "is_public": true,
44
+ "description": "다크모드 기능 활성화"
45
+ },
46
+ {
47
+ "key": "notification.email_enabled",
48
+ "category": "notification",
49
+ "value": "true",
50
+ "is_public": false,
51
+ "description": "이메일 알림 활성화"
52
+ }
53
+ ]
54
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "article",
3
+ "description": "이력 보존(history_ttl) & 완전삭제(hard_delete) 예제 — 게시글 수정 이력 3년 보관",
4
+ "index": {
5
+ "author_seq": {
6
+ "comment": "작성자 user seq",
7
+ "required": true
8
+ },
9
+ "title": {
10
+ "comment": "제목",
11
+ "required": true
12
+ },
13
+ "category": {
14
+ "comment": "카테고리",
15
+ "type": ["notice", "blog", "faq", "news"],
16
+ "default": "blog"
17
+ },
18
+ "status": {
19
+ "comment": "게시 상태",
20
+ "type": ["draft", "published", "archived"],
21
+ "default": "draft"
22
+ },
23
+ "published_at": {
24
+ "comment": "게시일시 (*_at → DATETIME 자동 추론)"
25
+ },
26
+ "view_count": {
27
+ "comment": "조회 수 (*_count → INT 자동 추론)"
28
+ }
29
+ },
30
+ "types": {
31
+ "content": "text",
32
+ "summary": "text",
33
+ "tags_json": "text"
34
+ },
35
+ "comments": {
36
+ "content": "본문 (마크다운)",
37
+ "summary": "요약 (목록 표시용)",
38
+ "tags_json": "태그 배열 JSON"
39
+ },
40
+ "history_ttl": 1095,
41
+ "hard_delete": false
42
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "workspace",
3
+ "description": "license_scope 예제 — 멀티테넌트에서 라이선스별 데이터 완전 분리",
4
+ "license_scope": true,
5
+ "index": {
6
+ "name": {
7
+ "comment": "워크스페이스명",
8
+ "required": true
9
+ },
10
+ "plan": {
11
+ "comment": "플랜",
12
+ "type": ["free", "starter", "pro", "enterprise"],
13
+ "default": "free"
14
+ },
15
+ "max_members": {
16
+ "comment": "최대 팀원 수 (자동추론: 직접 uint 선언)"
17
+ },
18
+ "owner_seq": {
19
+ "comment": "소유자 user seq"
20
+ },
21
+ "is_active": {
22
+ "comment": "활성 여부 (is_* → TINYINT(1) 자동 추론)",
23
+ "default": true
24
+ },
25
+ "expires_at": {
26
+ "comment": "만료일시 (*_at → DATETIME 자동 추론)"
27
+ }
28
+ },
29
+ "types": {
30
+ "max_members": "uint",
31
+ "settings_json": "text"
32
+ },
33
+ "comments": {
34
+ "settings_json": "워크스페이스 설정 JSON"
35
+ },
36
+ "defaults": {
37
+ "max_members": 5
38
+ },
39
+ "cache": {
40
+ "enabled": true,
41
+ "ttl_seconds": 120
42
+ }
43
+ }
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "user_point",
3
+ "description": "SQL 훅 예제 — INSERT/UPDATE 시 자동 감사 로그 기록 (실행형) + SELECT 결과 주입 (조회형)",
4
+ "index": {
5
+ "user_seq": {
6
+ "comment": "사용자 seq",
7
+ "required": true,
8
+ "unique": true
9
+ },
10
+ "point": {
11
+ "comment": "보유 포인트",
12
+ "type": "uint"
13
+ },
14
+ "grade": {
15
+ "comment": "등급",
16
+ "type": ["bronze", "silver", "gold", "platinum"],
17
+ "default": "bronze"
18
+ },
19
+ "updated_at": {
20
+ "comment": "마지막 포인트 변경일시 (*_at → DATETIME 자동 추론)"
21
+ }
22
+ },
23
+ "hooks": {
24
+ "after_insert": [
25
+ {
26
+ "comment": "포인트 생성 이력 기록 (실행형 SQL 훅)",
27
+ "type": "sql",
28
+ "query": "INSERT INTO point_history (user_seq, delta, reason, created_time) VALUES (?, ?, ?, NOW())",
29
+ "params": ["${new.user_seq}", "${new.point}", "initial_grant"],
30
+ "async": false
31
+ }
32
+ ],
33
+ "after_update": [
34
+ {
35
+ "comment": "포인트 변경 이력 기록 (실행형 SQL 훅)",
36
+ "type": "sql",
37
+ "query": "INSERT INTO point_history (user_seq, delta, reason, created_time) VALUES (?, ?, ?, NOW())",
38
+ "params": ["${new.user_seq}", "${new.point}", "update"],
39
+ "async": true
40
+ }
41
+ ],
42
+ "after_get": [
43
+ {
44
+ "comment": "최근 포인트 변경 이력 5건 주입 (조회형 SQL 훅)",
45
+ "type": "sql",
46
+ "query": "SELECT delta, reason, created_time FROM point_history WHERE user_seq = ? ORDER BY created_time DESC LIMIT 5",
47
+ "params": ["${new.user_seq}"],
48
+ "assign_to": "recent_history"
49
+ }
50
+ ]
51
+ }
52
+ }
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "post",
3
+ "description": "entity 훅 예제 — after_get/after_list 시 관련 엔티티 데이터를 자동 주입",
4
+ "index": {
5
+ "author_seq": {
6
+ "comment": "작성자 user seq",
7
+ "required": true
8
+ },
9
+ "title": {
10
+ "comment": "제목",
11
+ "required": true
12
+ },
13
+ "status": {
14
+ "comment": "상태",
15
+ "type": ["draft", "published", "hidden"],
16
+ "default": "draft"
17
+ },
18
+ "comment_count": {
19
+ "comment": "댓글 수 (*_count → INT 자동 추론)"
20
+ },
21
+ "like_count": {
22
+ "comment": "좋아요 수 (*_count → INT 자동 추론)"
23
+ },
24
+ "published_at": {
25
+ "comment": "게시일시 (*_at → DATETIME 자동 추론)"
26
+ }
27
+ },
28
+ "types": {
29
+ "content": "text"
30
+ },
31
+ "comments": {
32
+ "content": "본문"
33
+ },
34
+ "hooks": {
35
+ "after_get": [
36
+ {
37
+ "comment": "게시글 조회 시 작성자 프로필 자동 주입",
38
+ "type": "entity",
39
+ "entity": "user",
40
+ "action": "get",
41
+ "conditions": {
42
+ "seq": "${new.author_seq}"
43
+ },
44
+ "assign_to": "author"
45
+ },
46
+ {
47
+ "comment": "게시글 조회 시 최근 댓글 10개 자동 주입",
48
+ "type": "entity",
49
+ "entity": "comment",
50
+ "action": "list",
51
+ "conditions": {
52
+ "post_seq": "${new.seq}",
53
+ "status": "visible"
54
+ },
55
+ "assign_to": "comments"
56
+ }
57
+ ],
58
+ "after_list": [
59
+ {
60
+ "comment": "목록 조회 시 각 게시글에 작성자 이름 주입",
61
+ "type": "entity",
62
+ "entity": "user",
63
+ "action": "get",
64
+ "conditions": {
65
+ "seq": "${new.author_seq}"
66
+ },
67
+ "assign_to": "author"
68
+ }
69
+ ]
70
+ }
71
+ }