create-entity-server 0.0.9 → 0.0.23

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 (56) hide show
  1. package/bin/create.js +26 -8
  2. package/package.json +1 -1
  3. package/template/.env.example +20 -3
  4. package/template/configs/database.json +173 -10
  5. package/template/configs/jwt.json +1 -0
  6. package/template/configs/oauth.json +37 -0
  7. package/template/configs/push.json +26 -0
  8. package/template/entities/Account/account_audit.json +4 -5
  9. package/template/entities/README.md +4 -4
  10. package/template/entities/{Auth → System/Auth}/account.json +0 -14
  11. package/template/entities/System/system_audit_log.json +14 -8
  12. package/template/samples/README.md +43 -21
  13. package/template/samples/browser/entity-server-client.js +453 -0
  14. package/template/samples/browser/example.html +498 -0
  15. package/template/samples/entities/01_basic_fields.json +39 -0
  16. package/template/samples/entities/02_types_and_defaults.json +67 -0
  17. package/template/samples/entities/03_hash_and_unique.json +33 -0
  18. package/template/samples/entities/04_fk_and_composite_unique.json +29 -0
  19. package/template/samples/entities/05_cache.json +55 -0
  20. package/template/samples/entities/06_history_and_hard_delete.json +60 -0
  21. package/template/samples/entities/07_license_scope.json +52 -0
  22. package/template/samples/entities/08_hook_sql.json +52 -0
  23. package/template/samples/entities/09_hook_entity.json +65 -0
  24. package/template/samples/entities/10_hook_submit_delete.json +78 -0
  25. package/template/samples/entities/11_hook_webhook.json +84 -0
  26. package/template/samples/entities/12_hook_push.json +73 -0
  27. package/template/samples/entities/13_read_only.json +54 -0
  28. package/template/samples/entities/14_optimistic_lock.json +29 -0
  29. package/template/samples/entities/15_reset_defaults.json +94 -0
  30. package/template/samples/entities/16_isolated_license.json +62 -0
  31. package/template/samples/entities/README.md +91 -0
  32. package/template/samples/flutter/lib/entity_server_client.dart +261 -48
  33. package/template/samples/java/EntityServerClient.java +325 -61
  34. package/template/samples/java/EntityServerExample.java +4 -3
  35. package/template/samples/kotlin/EntityServerClient.kt +261 -45
  36. package/template/samples/node/src/EntityServerClient.js +348 -59
  37. package/template/samples/node/src/example.js +9 -9
  38. package/template/samples/php/ci4/Config/EntityServer.php +14 -0
  39. package/template/samples/php/ci4/Controllers/EntityController.php +202 -0
  40. package/template/samples/php/ci4/Controllers/ProductController.php +16 -76
  41. package/template/samples/php/ci4/Libraries/EntityServer.php +352 -60
  42. package/template/samples/php/laravel/Services/EntityServerService.php +245 -40
  43. package/template/samples/python/entity_server.py +287 -68
  44. package/template/samples/python/example.py +7 -6
  45. package/template/samples/react/src/example.tsx +41 -25
  46. package/template/samples/swift/EntityServerClient.swift +248 -37
  47. package/template/scripts/normalize-entities.sh +10 -10
  48. package/template/scripts/run.ps1 +12 -3
  49. package/template/scripts/run.sh +120 -37
  50. package/template/scripts/update-server.ps1 +160 -4
  51. package/template/scripts/update-server.sh +132 -4
  52. package/template/samples/react/src/api/entityServerClient.ts +0 -290
  53. package/template/samples/react/src/hooks/useEntity.ts +0 -105
  54. /package/template/entities/{Auth → System/Auth}/api_keys.json +0 -0
  55. /package/template/entities/{Auth → System/Auth}/license.json +0 -0
  56. /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"),
@@ -219,11 +228,14 @@ async function run() {
219
228
  );
220
229
  console.warn(` https://github.com/${REPO}/releases\n`);
221
230
  } else {
231
+ const binDir = path.join(targetDir, "bin");
232
+ fs.mkdirSync(binDir, { recursive: true });
233
+
222
234
  for (const bin of ["entity-server", "entity-cli"]) {
223
235
  const ext = platform === "windows" ? ".exe" : "";
224
236
  const fileName = `${bin}-${platform}-${arch}${ext}`;
225
237
  const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${fileName}`;
226
- const dest = path.join(targetDir, bin + ext);
238
+ const dest = path.join(binDir, bin + ext);
227
239
 
228
240
  process.stdout.write(` ↓ ${fileName} 다운로드 중...`);
229
241
  try {
@@ -244,8 +256,9 @@ async function run() {
244
256
  // ── 3. 완료 안내 ─────────────────────────────────────────────────────────
245
257
 
246
258
  const relDir = path.relative(process.cwd(), targetDir) || dirName;
247
- const serverExe = isWin ? ".\\entity-server.exe" : "./entity-server";
248
- const cliExe = isWin ? ".\\entity-cli.exe" : "./entity-cli";
259
+ const serverExe = isWin
260
+ ? ".\\bin\\entity-server.exe"
261
+ : "./bin/entity-server";
249
262
  const runScript = isWin
250
263
  ? ".\\scripts\\run.ps1 start"
251
264
  : "./scripts/run.sh start";
@@ -255,17 +268,22 @@ async function run() {
255
268
  ✓ 완료!
256
269
 
257
270
  ${relDir}/
258
- ├── entity-server${binExt} ← 서버 바이너리
259
- ├── entity-cli${binExt} CLI 도구
271
+ ├── bin/
272
+ ├── entity-server${binExt} 서버 바이너리
273
+ │ └── entity-cli${binExt} ← CLI 도구
260
274
  ├── .env ← 환경 변수 설정 (여기를 먼저 수정하세요)
261
275
  ├── configs/ ← CORS, JWT 등 서버 설정
262
276
  ├── entities/ ← 엔티티 스키마 JSON (샘플 포함)
263
- └── scripts/ ← 운영 스크립트
277
+ ├── scripts/ ← 운영 스크립트
278
+ └── samples/ ← 샘플 파일
264
279
 
265
280
  다음 단계:
266
281
  ${cdCmd ? ` ${cdCmd}` : ""}
267
- nano .env # PORT, DB_PATH, 암호화 설정
268
- ${serverExe} # 서버 실행
282
+ ./scripts/generate-env-keys${isWin ? ".ps1 -Apply" : ".sh --apply"} # ENCRYPTION_KEY/JWT_SECRET 생성 .env 반영
283
+ vim .env # PORT, DB_PATH 등 기본 환경값 확인/수정
284
+ vim configs/*.json # database/server/jwt 등 운영 설정
285
+ vim entities/*.json # 사용할 엔티티 스키마 정의
286
+ ${serverExe} # 마지막: 서버 실행
269
287
 
270
288
  서버마다 .env 의 PORT 와 DB_PATH 를 다르게 설정하면
271
289
  같은 머신에서 여러 프로젝트를 동시에 운영할 수 있습니다.
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.23",
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",
@@ -1,14 +1,31 @@
1
1
  # Entity Server 환경변수 설정 예시
2
2
  # 실제 사용시 이 파일을 .env로 복사하고 값을 변경하세요: cp .env.example .env
3
+ # ./scripts/generate-env-keys.sh 를 사용하여 랜덤 시크릿을 생성할 수 있습니다.
3
4
 
4
- # 기본 암복호화 (license 엔티티 등 공통 fallback 키)
5
- # 32자 16진수 문자열 (128bit AES-CTR 키)
5
+ # 기본 암복호화 마스터 시크릿 (license 엔티티 등 공통 fallback 키)
6
+ # HKDF-SHA256 으로 32바이트(256-bit) XChaCha20-Poly1305 키를 유도하는 마스터 시크릿입니다.
7
+ # 충분한 엔트로피를 가진 임의 문자열이면 됩니다 (예: openssl rand -hex 32 으로 생성).
6
8
  ENCRYPTION_KEY=your-32-char-hex-encryption-key-here
7
9
 
8
10
  # JWT 서명 키 (HS256)
9
11
  # 운영에서는 충분히 긴 랜덤 시크릿을 사용하세요.
10
12
  JWT_SECRET=your-jwt-secret-here
11
13
 
12
- # Database passwords (database.json의 password_env와 매핑)
14
+ # 서버 포트 (configs/server.json의 port를 오버라이드)
15
+ SERVER_PORT=47200
16
+
17
+ # Database values (database.json의 ${ENV_VAR}와 매핑)
18
+ DB_HOST_DEVELOPMENT=127.0.0.1
19
+ DB_PORT_DEVELOPMENT=3306
20
+ DB_NAME_DEVELOPMENT=your-development-db-name
21
+ DB_USER_DEVELOPMENT=your-development-db-user
13
22
  DB_PASSWORD_DEVELOPMENT=your-development-db-password
23
+
24
+ DB_HOST_PRODUCTION=127.0.0.1
25
+ DB_PORT_PRODUCTION=3306
26
+ DB_NAME_PRODUCTION=your-production-db-name
27
+ DB_USER_PRODUCTION=your-production-db-user
14
28
  DB_PASSWORD_PRODUCTION=your-production-db-password
29
+
30
+ # 푸시 알림 (push.json에서 ${FCM_PROJECT_ID} 등으로 참조 가능)
31
+ # FCM_PROJECT_ID=your-firebase-project-id
@@ -1,23 +1,186 @@
1
+ // ──────────────────────────────────────────────────────────────────────────
2
+ // database.json 설정 예시 (주석 포함 예시 전용 파일 — 실제 파일은 순수 JSON)
3
+ //
4
+ // "default" : 사용할 그룹 이름 (groups 키 중 하나)
5
+ // "groups" : 드라이버별 연결 설정 모음
6
+ // driver : "mysql" | "postgres" | "mongodb" | "dynamodb" |
7
+ // "firestore" | "scylladb" | "couchdb"
8
+ //
9
+ // 환경 변수 참조: "${ENV_VAR_NAME}" 형식으로 값을 주입합니다.
10
+ // ──────────────────────────────────────────────────────────────────────────
1
11
  {
2
12
  "default": "development",
3
13
  "groups": {
14
+ // ── MySQL (SQL) ─────────────────────────────────────────────────
4
15
  "development": {
5
16
  "driver": "mysql",
6
- "host": "127.0.0.1",
7
- "port": 3306,
8
- "database": "entity_server",
9
- "user": "root",
17
+ "host": "${DB_HOST_DEVELOPMENT}",
18
+ "port": "${DB_PORT_DEVELOPMENT}",
19
+ "database": "${DB_NAME_DEVELOPMENT}",
20
+ "user": "${DB_USER_DEVELOPMENT}",
10
21
  "password": "${DB_PASSWORD_DEVELOPMENT}",
11
- "maxOpenConns": 20
22
+ "maxOpenConns": 20,
23
+ "maxIdleConns": 10,
24
+ "connMaxLifetimeSec": 3600
12
25
  },
13
26
  "production": {
14
27
  "driver": "mysql",
15
- "host": "127.0.0.1",
16
- "port": 3306,
17
- "database": "entity_server",
18
- "user": "entity_user",
28
+ "host": "${DB_HOST_PRODUCTION}",
29
+ "port": "${DB_PORT_PRODUCTION}",
30
+ "database": "${DB_NAME_PRODUCTION}",
31
+ "user": "${DB_USER_PRODUCTION}",
19
32
  "password": "${DB_PASSWORD_PRODUCTION}",
20
- "maxOpenConns": 50
33
+ "maxOpenConns": 50,
34
+ "maxIdleConns": 25,
35
+ "connMaxLifetimeSec": 3600
36
+ },
37
+
38
+ // ── MongoDB ─────────────────────────────────────────────────────
39
+ // URI 우선 사용. uri 가 없으면 host/port/database/username/password 조합.
40
+ "mongodb_dev": {
41
+ "driver": "mongodb",
42
+ "uri": "mongodb://localhost:27017/entity_dev",
43
+ "database": "entity_dev"
44
+ },
45
+ "mongodb_prod": {
46
+ "driver": "mongodb",
47
+ "host": "${MONGO_HOST}",
48
+ "port": 27017,
49
+ "database": "${MONGO_DB}",
50
+ "username": "${MONGO_USER}",
51
+ "password": "${MONGO_PASSWORD}",
52
+ "uri": ""
53
+ },
54
+ "mongodb_replica": {
55
+ "driver": "mongodb",
56
+ "uri": "mongodb://${MONGO_USER}:${MONGO_PASS}@mongo1:27017,mongo2:27017,mongo3:27017/${MONGO_DB}?replicaSet=rs0&authSource=admin",
57
+ "database": "${MONGO_DB}"
58
+ },
59
+
60
+ // ── Amazon DynamoDB ─────────────────────────────────────────────
61
+ // region 과 IAM 인증이 필요합니다.
62
+ // 로컬 테스트: endpoint 에 "http://localhost:8000" 지정.
63
+ "dynamodb_dev": {
64
+ "driver": "dynamodb",
65
+ "region": "ap-northeast-2",
66
+ "endpoint": "http://localhost:8000",
67
+ "access_key_id": "${AWS_ACCESS_KEY_ID}",
68
+ "secret_access_key": "${AWS_SECRET_ACCESS_KEY}"
69
+ },
70
+ "dynamodb_prod": {
71
+ "driver": "dynamodb",
72
+ "region": "${AWS_REGION}",
73
+ "access_key_id": "${AWS_ACCESS_KEY_ID}",
74
+ "secret_access_key": "${AWS_SECRET_ACCESS_KEY}"
75
+ },
76
+
77
+ // ── Google Cloud Firestore ──────────────────────────────────────
78
+ // GCP 프로젝트 ID 와 서비스 계정 JSON 파일(또는 ADC)이 필요합니다.
79
+ // credentials_file : 서비스 계정 키 파일 경로 (선택)
80
+ // ADC 자동 사용 시 credentials_file 생략 가능.
81
+ "firestore_dev": {
82
+ "driver": "firestore",
83
+ "project_id": "${GCP_PROJECT_ID}",
84
+ "credentials_file": "${GOOGLE_APPLICATION_CREDENTIALS}"
85
+ },
86
+ "firestore_prod": {
87
+ "driver": "firestore",
88
+ "project_id": "${GCP_PROJECT_ID}"
89
+ },
90
+
91
+ // ── ScyllaDB / Apache Cassandra ────────────────────────────────
92
+ // hosts 는 쉼표 구분 문자열 또는 배열 모두 허용합니다.
93
+ // keyspace 가 없으면 자동 생성됩니다.
94
+ "scylladb_dev": {
95
+ "driver": "scylladb",
96
+ "hosts": "localhost:9042",
97
+ "keyspace": "entity_dev",
98
+ "username": "",
99
+ "password": ""
100
+ },
101
+ "scylladb_prod": {
102
+ "driver": "scylladb",
103
+ "hosts": "${SCYLLA_HOSTS}",
104
+ "keyspace": "${SCYLLA_KEYSPACE}",
105
+ "username": "${SCYLLA_USER}",
106
+ "password": "${SCYLLA_PASSWORD}",
107
+ "consistency": "quorum",
108
+ "timeout": "10s"
109
+ },
110
+
111
+ // ── Apache CouchDB ──────────────────────────────────────────────
112
+ // URI 우선 사용. uri 가 없으면 host/port/username/password 조합.
113
+ // 포트 기본값: 5984
114
+ "couchdb_dev": {
115
+ "driver": "couchdb",
116
+ "host": "localhost",
117
+ "port": 5984,
118
+ "username": "admin",
119
+ "password": "password"
120
+ },
121
+ "couchdb_uri": {
122
+ "driver": "couchdb",
123
+ "uri": "http://admin:password@localhost:5984/"
124
+ },
125
+ "couchdb_prod": {
126
+ "driver": "couchdb",
127
+ "uri": "http://${COUCH_USER}:${COUCH_PASS}@${COUCH_HOST}:${COUCH_PORT}/"
21
128
  }
22
129
  }
23
130
  }
131
+
132
+ // ──────────────────────────────────────────────────────────────────────────
133
+ // DataStore 단독 사용 — flat 형식 (groups 없이 최상위에 driver 직접 지정)
134
+ //
135
+ // SQL 없이 NoSQL DataStore만 단독으로 사용할 때는 groups 없이
136
+ // database.json 최상위에 driver 를 직접 지정합니다.
137
+ // store_loader.go 가 groups 키가 없고 NoSQL driver 인 경우에만 DataStore로 인식합니다.
138
+ // ──────────────────────────────────────────────────────────────────────────
139
+
140
+ // MongoDB flat 형식
141
+ {
142
+ "driver": "mongodb",
143
+ "uri": "mongodb://localhost:27017",
144
+ "database": "entity_server"
145
+ }
146
+
147
+ // MongoDB — 환경변수 방식
148
+ {
149
+ "driver": "mongodb",
150
+ "host": "${MONGO_HOST}",
151
+ "port": "${MONGO_PORT}",
152
+ "username": "${MONGO_USER}",
153
+ "password": "${MONGO_PASSWORD}",
154
+ "database": "${MONGO_DATABASE}"
155
+ }
156
+
157
+ // DynamoDB flat 형식
158
+ {
159
+ "driver": "dynamodb",
160
+ "region": "${AWS_REGION}",
161
+ "access_key_id": "${AWS_ACCESS_KEY_ID}",
162
+ "secret_access_key": "${AWS_SECRET_ACCESS_KEY}"
163
+ }
164
+
165
+ // Firestore flat 형식
166
+ {
167
+ "driver": "firestore",
168
+ "project_id": "${GCP_PROJECT_ID}",
169
+ "credentials_file": "${GOOGLE_APPLICATION_CREDENTIALS}"
170
+ }
171
+
172
+ // ScyllaDB flat 형식
173
+ {
174
+ "driver": "scylladb",
175
+ "host": "${SCYLLA_HOST}",
176
+ "port": 9042,
177
+ "database": "${SCYLLA_KEYSPACE}",
178
+ "username": "${SCYLLA_USER}",
179
+ "password": "${SCYLLA_PASSWORD}"
180
+ }
181
+
182
+ // CouchDB flat 형식
183
+ {
184
+ "driver": "couchdb",
185
+ "uri": "http://${COUCH_USER}:${COUCH_PASS}@${COUCH_HOST}:5984/"
186
+ }
@@ -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
+ }
@@ -1,17 +1,16 @@
1
1
  {
2
2
  "name": "account_audit",
3
- "description": "account_audit Entity",
3
+ "description": "account 감사 로그. JWT 인증 시에만 account_seq는 required",
4
4
  "index": {
5
5
  "account_seq": {
6
- "comment": "계정 seq",
6
+ "comment": "계정 seq (JWT 인증 시 account.seq 참조. HMAC은 nullable)",
7
7
  "type": "bigint",
8
- "required": true
8
+ "nullable": true
9
9
  },
10
10
  "action": {
11
11
  "comment": "작업 유형",
12
12
  "type": ["INSERT", "UPDATE", "DELETE", "LOGIN", "LOGOUT"],
13
13
  "required": true
14
14
  }
15
- },
16
- "license_scope": false
15
+ }
17
16
  }
@@ -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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "system_audit_log",
3
- "description": "시스템 감사 로그. 서버 레벨에서 자동 기록되며 API를 통한 직접 수정은 허용되지 않습니다.",
3
+ "description": "시스템 감사 로그. 서버 레벨에서 자동 기록되며 API를 통한 직접 수정은 허용되지 않습니다. JWT 인증 시에만 account_seq 기록",
4
4
  "db_group": "system",
5
5
  "hard_delete": true,
6
6
  "history_ttl": 0,
@@ -18,7 +18,8 @@
18
18
  },
19
19
  "entity_seq": {
20
20
  "comment": "대상 엔티티 레코드 seq. 로그인/로그아웃 등 레코드 없는 경우 NULL",
21
- "type": "bigint"
21
+ "type": "bigint",
22
+ "nullable": true
22
23
  },
23
24
  "action": {
24
25
  "comment": "수행된 작업 유형",
@@ -34,16 +35,19 @@
34
35
  "required": true
35
36
  },
36
37
  "account_seq": {
37
- "comment": "작업을 수행한 계정 seq. 비인증 요청은 NULL",
38
- "type": "bigint"
38
+ "comment": "작업을 수행한 계정 seq. JWT 인증 요청은 account.seq, 비인증/HMAC 요청은 NULL",
39
+ "type": "bigint",
40
+ "nullable": true
39
41
  },
40
42
  "ip_address": {
41
43
  "comment": "요청 IP 주소 (IPv4/IPv6)",
42
- "type": "varchar(45)"
44
+ "type": "varchar(45)",
45
+ "nullable": true
43
46
  },
44
47
  "endpoint": {
45
48
  "comment": "요청 API 엔드포인트",
46
- "type": "varchar(200)"
49
+ "type": "varchar(200)",
50
+ "nullable": true
47
51
  },
48
52
  "request_method": {
49
53
  "comment": "HTTP 메서드 (GET/POST/PUT/DELETE 등)",
@@ -51,7 +55,8 @@
51
55
  },
52
56
  "request_payload": {
53
57
  "comment": "요청 본문 JSON. 민감 필드(password, token 등) 자동 마스킹 후 저장. ServerConfig.AuditLogPayload = true 일 때만 기록",
54
- "type": "text"
58
+ "type": "text",
59
+ "nullable": true
55
60
  },
56
61
  "result_code": {
57
62
  "comment": "HTTP 응답 코드 (200, 400, 401, 403, 500 등)",
@@ -59,7 +64,8 @@
59
64
  },
60
65
  "error_message": {
61
66
  "comment": "실패 시 오류 메시지 요약",
62
- "type": "varchar(500)"
67
+ "type": "varchar(500)",
68
+ "nullable": true
63
69
  }
64
70
  }
65
71
  }
@@ -15,11 +15,12 @@ Client / Browser
15
15
  Backend Server ←── 이 샘플이 구현하는 부분
16
16
 
17
17
  ▼ HMAC 서명 (서버 간 통신)
18
- Entity Server (Go)
18
+ Entity Server
19
19
  ```
20
20
 
21
- > **React (SPA)** 샘플은 HMAC 대신 JWT Bearer 토큰을 사용합니다.
22
- > 브라우저 환경에서는 HMAC secret노출할 없기 때문입니다.
21
+ > **브라우저(React·Vanilla)** 환경에서도 HMAC / JWT 모두 사용 가능합니다.
22
+ > 단, 브라우저에서 HMAC을 사용하면 `hmacSecret`이 클라이언트에 노출되므로,
23
+ > **프론트엔드 프로덕션 환경에서는 JWT 사용을 권장합니다.**
23
24
 
24
25
  ## HMAC 서명 공식
25
26
 
@@ -39,27 +40,48 @@ signature = HMAC-SHA256(hmacSecret, payload) → hex
39
40
 
40
41
  ## API 엔드포인트
41
42
 
42
- | 동작 | 메서드 | 경로 |
43
- | --------- | ------ | ---------------------------------- |
44
- | 단건 조회 | GET | `/v1/entity/{name}/{seq}` |
45
- | 목록 조회 | GET | `/v1/entity/{name}/list` |
46
- | 필터 검색 | POST | `/v1/entity/{name}/query` |
47
- | 건수 조회 | GET | `/v1/entity/{name}/count` |
48
- | 생성/수정 | POST | `/v1/entity/{name}/submit` |
49
- | 삭제 | DELETE | `/v1/entity/{name}/delete/{seq}` |
50
- | 이력 조회 | GET | `/v1/entity/{name}/history/{seq}` |
51
- | 롤백 | POST | `/v1/entity/{name}/rollback/{seq}` |
43
+ | 동작 | 메서드 | 경로 |
44
+ | -------------- | ------ | ---------------------------------- |
45
+ | 단건 조회 | GET | `/v1/entity/{name}/{seq}` |
46
+ | 조건 단건 조회 | POST | `/v1/entity/{name}/find` |
47
+ | 목록 조회 | POST | `/v1/entity/{name}/list` |
48
+ | 필터 검색 | POST | `/v1/entity/{name}/query` |
49
+ | 건수 조회 | POST | `/v1/entity/{name}/count` |
50
+ | 생성/수정 | POST | `/v1/entity/{name}/submit` |
51
+ | 삭제 | POST | `/v1/entity/{name}/delete/{seq}` |
52
+ | 이력 조회 | GET | `/v1/entity/{name}/history/{seq}` |
53
+ | 롤백 | POST | `/v1/entity/{name}/rollback/{seq}` |
52
54
 
53
55
  - `list` 쿼리 파라미터: `?page=1&limit=20&order_by=<field>`
54
56
  - `submit` — body에 `seq` 포함 시 수정, 없으면 생성
55
57
 
56
58
  ## 샘플 목록
57
59
 
58
- | 디렉토리 | 프레임워크 | 인증 방식 |
59
- | -------------- | ---------------------- | --------- |
60
- | `php/ci4/` | CodeIgniter 4 | HMAC |
61
- | `php/laravel/` | Laravel | HMAC |
62
- | `java/` | Java (표준 라이브러리) | HMAC |
63
- | `node/` | Node.js (fetch) | HMAC |
64
- | `python/` | Python (requests) | HMAC |
65
- | `react/` | React + TypeScript | JWT |
60
+ | 디렉토리 | 프레임워크 | 인증 방식 |
61
+ | -------------- | ---------------------------------------------- | ---------- |
62
+ | `entities/` | 엔티티 설정 예제 | |
63
+ | `browser/` | 브라우저 (Vanilla ES Module, 빌드 도구 불필요) | HMAC / JWT |
64
+ | `php/ci4/` | CodeIgniter 4 | HMAC / JWT |
65
+ | `php/laravel/` | Laravel | HMAC / JWT |
66
+ | `java/` | Java (표준 라이브러리) | HMAC / JWT |
67
+ | `kotlin/` | Kotlin (표준 라이브러리) | HMAC / JWT |
68
+ | `swift/` | Swift (URLSession) | HMAC / JWT |
69
+ | `flutter/` | Flutter (Dart) | HMAC / JWT |
70
+ | `node/` | Node.js (fetch) | HMAC / JWT |
71
+ | `python/` | Python (requests) | HMAC / JWT |
72
+ | `react/` | React + TypeScript | HMAC / JWT |
73
+
74
+ ### CI4 설정 방식
75
+
76
+ - `php/ci4` 샘플은 `app/Config/EntityServer.php` 중심으로 설정을 관리합니다.
77
+ - 다른 언어 샘플은 각 라이브러리/클라이언트 파일 내부 설정값을 직접 수정해 사용합니다.
78
+
79
+ ### Push 헬퍼
80
+
81
+ - 모든 샘플 클라이언트는 푸시 트리거용 헬퍼를 제공합니다. - `push(pushEntity, payload[, transactionId])` : 내부적으로 `submit(pushEntity, payload)` 호출 - `pushLogList(...)` : 내부적으로 `list('push_log', ...)` 호출
82
+ - Python 샘플은 PEP8 네이밍으로 `push_log_list(...)` 메서드를 제공합니다.
83
+
84
+ ### 요청 본문(암호 패킷) 읽기 헬퍼
85
+
86
+ - 모든 샘플에 요청 본문 파싱 헬퍼를 제공합니다. - `application/octet-stream`이면 XChaCha20-Poly1305 패킷을 복호화해 JSON 반환 - 그 외 Content-Type이면 평문 JSON 파싱 - 옵션으로 `requireEncrypted=true` 정책을 줄 수 있습니다.
87
+ - 언어별 메서드명 - Node/React/Kotlin/Java/Swift/Laravel: `readRequestBody(...)` - Python: `read_request_body(...)` - Flutter: `readRequestBody(...)`