create-entity-server 0.0.9

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 (63) hide show
  1. package/bin/create.js +280 -0
  2. package/package.json +42 -0
  3. package/template/.env.example +14 -0
  4. package/template/configs/cache.json +22 -0
  5. package/template/configs/cors.json +7 -0
  6. package/template/configs/database.json +23 -0
  7. package/template/configs/jwt.json +7 -0
  8. package/template/configs/logging.json +45 -0
  9. package/template/configs/security.json +21 -0
  10. package/template/configs/server.json +10 -0
  11. package/template/entities/Account/account_audit.json +17 -0
  12. package/template/entities/Auth/account.json +60 -0
  13. package/template/entities/Auth/api_keys.json +26 -0
  14. package/template/entities/Auth/license.json +36 -0
  15. package/template/entities/Auth/rbac_roles.json +76 -0
  16. package/template/entities/README.md +380 -0
  17. package/template/entities/System/system_audit_log.json +65 -0
  18. package/template/entities/company.json +22 -0
  19. package/template/entities/product.json +36 -0
  20. package/template/entities/todo.json +16 -0
  21. package/template/samples/README.md +65 -0
  22. package/template/samples/flutter/lib/entity_server_client.dart +218 -0
  23. package/template/samples/flutter/pubspec.yaml +14 -0
  24. package/template/samples/java/EntityServerClient.java +304 -0
  25. package/template/samples/java/EntityServerExample.java +49 -0
  26. package/template/samples/kotlin/EntityServerClient.kt +194 -0
  27. package/template/samples/node/package.json +16 -0
  28. package/template/samples/node/src/EntityServerClient.js +246 -0
  29. package/template/samples/node/src/example.js +39 -0
  30. package/template/samples/php/ci4/Controllers/ProductController.php +141 -0
  31. package/template/samples/php/ci4/Libraries/EntityServer.php +260 -0
  32. package/template/samples/php/laravel/Http/Controllers/ProductController.php +62 -0
  33. package/template/samples/php/laravel/Services/EntityServerService.php +210 -0
  34. package/template/samples/python/entity_server.py +225 -0
  35. package/template/samples/python/example.py +50 -0
  36. package/template/samples/react/src/api/entityServerClient.ts +290 -0
  37. package/template/samples/react/src/example.tsx +127 -0
  38. package/template/samples/react/src/hooks/useEntity.ts +105 -0
  39. package/template/samples/swift/EntityServerClient.swift +221 -0
  40. package/template/scripts/api-key.ps1 +123 -0
  41. package/template/scripts/api-key.sh +130 -0
  42. package/template/scripts/cleanup-history.ps1 +69 -0
  43. package/template/scripts/cleanup-history.sh +54 -0
  44. package/template/scripts/cli.ps1 +24 -0
  45. package/template/scripts/cli.sh +27 -0
  46. package/template/scripts/entity.ps1 +70 -0
  47. package/template/scripts/entity.sh +72 -0
  48. package/template/scripts/generate-env-keys.ps1 +125 -0
  49. package/template/scripts/generate-env-keys.sh +148 -0
  50. package/template/scripts/install-systemd.sh +222 -0
  51. package/template/scripts/normalize-entities.ps1 +87 -0
  52. package/template/scripts/normalize-entities.sh +132 -0
  53. package/template/scripts/rbac-role.ps1 +124 -0
  54. package/template/scripts/rbac-role.sh +127 -0
  55. package/template/scripts/remove-systemd.sh +158 -0
  56. package/template/scripts/reset-all.ps1 +83 -0
  57. package/template/scripts/reset-all.sh +95 -0
  58. package/template/scripts/run.ps1 +239 -0
  59. package/template/scripts/run.sh +315 -0
  60. package/template/scripts/sync.ps1 +145 -0
  61. package/template/scripts/sync.sh +178 -0
  62. package/template/scripts/update-server.ps1 +117 -0
  63. package/template/scripts/update-server.sh +165 -0
package/bin/create.js ADDED
@@ -0,0 +1,280 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * create-entity-server
4
+ *
5
+ * CRA / create-vite 와 동일한 UX 로 entity-server 프로젝트를 초기화합니다.
6
+ *
7
+ * 사용법:
8
+ * npx create-entity-server → ./entity-server/ 생성
9
+ * npx create-entity-server my-api → ./my-api/ 생성
10
+ * npm create entity-server my-api → 동일
11
+ */
12
+
13
+ "use strict";
14
+
15
+ const https = require("https");
16
+ const http = require("http");
17
+ const fs = require("fs");
18
+ const path = require("path");
19
+ const os = require("os");
20
+
21
+ // ─── 버전 / 설정 ──────────────────────────────────────────────────────────────
22
+
23
+ const pkg = require("../package.json");
24
+ const VERSION = pkg.version;
25
+ const REPO = "ehfuse/entity-server";
26
+ const isWin = process.platform === "win32";
27
+ const binExt = isWin ? ".exe" : "";
28
+
29
+ const PLATFORM_MAP = { linux: "linux", darwin: "darwin", win32: "windows" };
30
+ const ARCH_MAP = { x64: "x64", arm64: "arm64" };
31
+ const platform = PLATFORM_MAP[os.platform()];
32
+ const arch = ARCH_MAP[os.arch()];
33
+
34
+ // ─── 도움말 ───────────────────────────────────────────────────────────────────
35
+
36
+ function printHelp() {
37
+ console.log(`
38
+ create-entity-server v${VERSION}
39
+
40
+ 사용법:
41
+ npx create-entity-server [폴더명]
42
+ npm create entity-server [폴더명]
43
+
44
+ 예시:
45
+ npx create-entity-server # → ./entity-server/
46
+ npx create-entity-server my-api # → ./my-api/
47
+ npx create-entity-server ./projects/order-api
48
+
49
+ 옵션:
50
+ --skip-binary 바이너리 다운로드 건너뜀 (나중에 수동 추가)
51
+ --help, -h 이 도움말 출력
52
+ `);
53
+ }
54
+
55
+ // ─── 헬퍼 ─────────────────────────────────────────────────────────────────────
56
+
57
+ function copyFile(src, dest, mode) {
58
+ if (!fs.existsSync(src)) return false;
59
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
60
+ fs.copyFileSync(src, dest);
61
+ if (mode != null) {
62
+ try {
63
+ fs.chmodSync(dest, mode);
64
+ } catch (_) {}
65
+ }
66
+ return true;
67
+ }
68
+
69
+ function copyDir(src, dest) {
70
+ if (!fs.existsSync(src)) return;
71
+ fs.mkdirSync(dest, { recursive: true });
72
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
73
+ const s = path.join(src, entry.name);
74
+ const d = path.join(dest, entry.name);
75
+ if (entry.isDirectory()) copyDir(s, d);
76
+ else fs.copyFileSync(s, d);
77
+ }
78
+ }
79
+
80
+ /** HTTP/HTTPS 리다이렉트를 따라가며 파일을 다운로드합니다. */
81
+ function download(url, dest, redirectCount = 0) {
82
+ return new Promise((resolve, reject) => {
83
+ if (redirectCount > 5) return reject(new Error("너무 많은 리다이렉트"));
84
+
85
+ const client = url.startsWith("https") ? https : http;
86
+ const req = client.get(url, (res) => {
87
+ if (
88
+ res.statusCode >= 300 &&
89
+ res.statusCode < 400 &&
90
+ res.headers.location
91
+ ) {
92
+ return download(res.headers.location, dest, redirectCount + 1)
93
+ .then(resolve)
94
+ .catch(reject);
95
+ }
96
+ if (res.statusCode !== 200) {
97
+ // 응답 body 를 버려야 소켓이 정리됨
98
+ res.resume();
99
+ return reject(new Error(`HTTP ${res.statusCode}: ${url}`));
100
+ }
101
+
102
+ const tmp = dest + ".tmp";
103
+ const file = fs.createWriteStream(tmp);
104
+ res.pipe(file);
105
+ file.on("finish", () => {
106
+ file.close(() => {
107
+ fs.renameSync(tmp, dest);
108
+ resolve();
109
+ });
110
+ });
111
+ file.on("error", (err) => {
112
+ try {
113
+ fs.unlinkSync(tmp);
114
+ } catch (_) {}
115
+ reject(err);
116
+ });
117
+ });
118
+
119
+ req.on("error", reject);
120
+ req.setTimeout(120_000, () => {
121
+ req.destroy();
122
+ reject(new Error("다운로드 타임아웃 (120s)"));
123
+ });
124
+ });
125
+ }
126
+
127
+ // ─── 메인 ─────────────────────────────────────────────────────────────────────
128
+
129
+ async function run() {
130
+ const argv = process.argv.slice(2);
131
+ const skipBinary = argv.includes("--skip-binary");
132
+ const filtered = argv.filter((a) => !a.startsWith("-"));
133
+
134
+ if (argv.includes("--help") || argv.includes("-h")) {
135
+ printHelp();
136
+ process.exit(0);
137
+ }
138
+
139
+ // 대상 폴더 결정
140
+ const dirName = filtered[0] ?? "entity-server";
141
+ const targetDir = path.resolve(process.cwd(), dirName);
142
+
143
+ // 이미 비어있지 않은 폴더면 중단
144
+ if (fs.existsSync(targetDir)) {
145
+ const entries = fs.readdirSync(targetDir);
146
+ if (entries.length > 0) {
147
+ console.error(
148
+ `\n오류: '${dirName}' 폴더가 이미 존재하고 비어있지 않습니다.`,
149
+ );
150
+ console.error(`다른 이름을 사용하거나 빈 폴더를 지정하세요.\n`);
151
+ process.exit(1);
152
+ }
153
+ }
154
+
155
+ const templateDir = path.resolve(__dirname, "..", "template");
156
+
157
+ console.log(`\n create-entity-server v${VERSION}`);
158
+ console.log(` 폴더 생성 중: ${targetDir}\n`);
159
+
160
+ // ── 1. 템플릿 파일 복사 ────────────────────────────────────────────────────
161
+
162
+ // scripts/ — OS별 파일만 복사
163
+ // Windows : .ps1 만 (쉘 스크립트 제외)
164
+ // Linux/macOS: .sh 만 (PowerShell 제외)
165
+ const scriptsSrc = path.join(templateDir, "scripts");
166
+ const scriptsDest = path.join(targetDir, "scripts");
167
+ if (fs.existsSync(scriptsSrc)) {
168
+ fs.mkdirSync(scriptsDest, { recursive: true });
169
+ for (const f of fs.readdirSync(scriptsSrc)) {
170
+ if (isWin && !f.endsWith(".ps1")) continue;
171
+ if (!isWin && !f.endsWith(".sh")) continue;
172
+ fs.copyFileSync(
173
+ path.join(scriptsSrc, f),
174
+ path.join(scriptsDest, f),
175
+ );
176
+ }
177
+ // Linux/macOS: 실행 권한 부여
178
+ if (!isWin) {
179
+ for (const f of fs.readdirSync(scriptsDest)) {
180
+ try {
181
+ fs.chmodSync(path.join(scriptsDest, f), 0o755);
182
+ } catch (_) {}
183
+ }
184
+ }
185
+ }
186
+
187
+ // configs/
188
+ copyDir(path.join(templateDir, "configs"), path.join(targetDir, "configs"));
189
+
190
+ // entities/ (샘플 스키마)
191
+ copyDir(
192
+ path.join(templateDir, "entities"),
193
+ path.join(targetDir, "entities"),
194
+ );
195
+
196
+ // .env.example 복사 + .env 생성 (없을 때만)
197
+ copyFile(
198
+ path.join(templateDir, ".env.example"),
199
+ path.join(targetDir, ".env.example"),
200
+ );
201
+ if (!fs.existsSync(path.join(targetDir, ".env"))) {
202
+ copyFile(
203
+ path.join(templateDir, ".env.example"),
204
+ path.join(targetDir, ".env"),
205
+ );
206
+ }
207
+
208
+ console.log(" ✓ 설정 파일 복사 완료");
209
+
210
+ // ── 2. 바이너리 다운로드 ──────────────────────────────────────────────────
211
+
212
+ if (!skipBinary) {
213
+ if (!platform || !arch) {
214
+ console.warn(
215
+ `\n 경고: 미지원 플랫폼 (${os.platform()}/${os.arch()})`,
216
+ );
217
+ console.warn(
218
+ ` --skip-binary 옵션 후 수동으로 바이너리를 복사하세요.`,
219
+ );
220
+ console.warn(` https://github.com/${REPO}/releases\n`);
221
+ } else {
222
+ for (const bin of ["entity-server", "entity-cli"]) {
223
+ const ext = platform === "windows" ? ".exe" : "";
224
+ const fileName = `${bin}-${platform}-${arch}${ext}`;
225
+ const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${fileName}`;
226
+ const dest = path.join(targetDir, bin + ext);
227
+
228
+ process.stdout.write(` ↓ ${fileName} 다운로드 중...`);
229
+ try {
230
+ await download(url, dest);
231
+ fs.chmodSync(dest, 0o755);
232
+ console.log(" ✓");
233
+ } catch (err) {
234
+ console.log(" ✗ 실패");
235
+ console.warn(` ${err.message}`);
236
+ console.warn(` 나중에 수동으로 다운로드하세요: ${url}`);
237
+ }
238
+ }
239
+ }
240
+ } else {
241
+ console.log(" ↷ 바이너리 건너뜀 (--skip-binary)");
242
+ }
243
+
244
+ // ── 3. 완료 안내 ─────────────────────────────────────────────────────────
245
+
246
+ 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";
249
+ const runScript = isWin
250
+ ? ".\\scripts\\run.ps1 start"
251
+ : "./scripts/run.sh start";
252
+ const cdCmd = relDir !== "." ? `cd ${relDir}` : "";
253
+
254
+ console.log(`
255
+ ✓ 완료!
256
+
257
+ ${relDir}/
258
+ ├── entity-server${binExt} ← 서버 바이너리
259
+ ├── entity-cli${binExt} ← CLI 도구
260
+ ├── .env ← 환경 변수 설정 (여기를 먼저 수정하세요)
261
+ ├── configs/ ← CORS, JWT 등 서버 설정
262
+ ├── entities/ ← 엔티티 스키마 JSON (샘플 포함)
263
+ └── scripts/ ← 운영 스크립트
264
+
265
+ 다음 단계:
266
+ ${cdCmd ? ` ${cdCmd}` : ""}
267
+ nano .env # PORT, DB_PATH, 암호화 키 설정
268
+ ${serverExe} # 서버 실행
269
+
270
+ 서버마다 .env 의 PORT 와 DB_PATH 를 다르게 설정하면
271
+ 같은 머신에서 여러 프로젝트를 동시에 운영할 수 있습니다.
272
+
273
+ 문서: https://github.com/${REPO}#readme
274
+ `);
275
+ }
276
+
277
+ run().catch((err) => {
278
+ console.error(`\n오류: ${err.message}\n`);
279
+ process.exit(1);
280
+ });
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "create-entity-server",
3
+ "version": "0.0.9",
4
+ "description": "Create a new entity-server project in one command — like create-react-app or create-vite.",
5
+ "keywords": [
6
+ "entity-server",
7
+ "create",
8
+ "scaffold",
9
+ "api-server",
10
+ "crud",
11
+ "rest-api",
12
+ "schema-driven"
13
+ ],
14
+ "homepage": "https://github.com/ehfuse/entity-server",
15
+ "bugs": {
16
+ "url": "https://github.com/ehfuse/entity-server/issues"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/ehfuse/entity-server.git"
21
+ },
22
+ "license": "MIT",
23
+ "bin": {
24
+ "create-entity-server": "bin/create.js"
25
+ },
26
+ "files": [
27
+ "bin/create.js",
28
+ "template/"
29
+ ],
30
+ "engines": {
31
+ "node": ">=18"
32
+ },
33
+ "os": [
34
+ "linux",
35
+ "darwin",
36
+ "win32"
37
+ ],
38
+ "cpu": [
39
+ "x64",
40
+ "arm64"
41
+ ]
42
+ }
@@ -0,0 +1,14 @@
1
+ # Entity Server 환경변수 설정 예시
2
+ # 실제 사용시 이 파일을 .env로 복사하고 값을 변경하세요: cp .env.example .env
3
+
4
+ # 기본 암복호화 키 (license 엔티티 등 공통 fallback 키)
5
+ # 32자 16진수 문자열 (128bit AES-CTR 키)
6
+ ENCRYPTION_KEY=your-32-char-hex-encryption-key-here
7
+
8
+ # JWT 서명 키 (HS256)
9
+ # 운영에서는 충분히 긴 랜덤 시크릿을 사용하세요.
10
+ JWT_SECRET=your-jwt-secret-here
11
+
12
+ # Database passwords (database.json의 password_env와 매핑)
13
+ DB_PASSWORD_DEVELOPMENT=your-development-db-password
14
+ DB_PASSWORD_PRODUCTION=your-production-db-password
@@ -0,0 +1,22 @@
1
+ {
2
+ "description": "전역 캐시 설정. driver는 memory | file | memcached(memcache) | redis 지원. 엔티티별 설정(cache)은 이 기본값을 override.",
3
+ "defaults": {
4
+ "enabled": true,
5
+ "driver": "memory",
6
+ "ttl_seconds": 60
7
+ },
8
+ "drivers": {
9
+ "file": {
10
+ "dir": ".cache/entity"
11
+ },
12
+ "memcached": {
13
+ "servers": ["127.0.0.1:11211"]
14
+ },
15
+ "redis": {
16
+ "addr": "127.0.0.1:6379",
17
+ "password": "",
18
+ "db": 0,
19
+ "prefix": "entity_cache:"
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "cors_enabled": true,
3
+ "cors_allow_credentials": false,
4
+ "cors_allow_headers": "Origin,Content-Type,Accept,Authorization,X-API-Key,X-Signature,X-Timestamp,X-Nonce,X-Transaction-ID",
5
+ "cors_allow_methods": "GET,POST,PUT,PATCH,DELETE,OPTIONS",
6
+ "cors_allow_origins": "*"
7
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "default": "development",
3
+ "groups": {
4
+ "development": {
5
+ "driver": "mysql",
6
+ "host": "127.0.0.1",
7
+ "port": 3306,
8
+ "database": "entity_server",
9
+ "user": "root",
10
+ "password": "${DB_PASSWORD_DEVELOPMENT}",
11
+ "maxOpenConns": 20
12
+ },
13
+ "production": {
14
+ "driver": "mysql",
15
+ "host": "127.0.0.1",
16
+ "port": 3306,
17
+ "database": "entity_server",
18
+ "user": "entity_user",
19
+ "password": "${DB_PASSWORD_PRODUCTION}",
20
+ "maxOpenConns": 50
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "secret": "${JWT_SECRET}",
3
+ "access_ttl_sec": 3600,
4
+ "refresh_ttl_sec": 1209600,
5
+ "issuer": "entity-server",
6
+ "algorithm": "HS256"
7
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "level": "INFO",
3
+ "directory": "logs",
4
+ "access": {
5
+ "enabled": true,
6
+ "filename": "access.log",
7
+ "max_size_mb": 100,
8
+ "max_backups": 7,
9
+ "max_age_days": 14,
10
+ "compress": true
11
+ },
12
+ "error": {
13
+ "enabled": true,
14
+ "filename": "error.log",
15
+ "max_size_mb": 100,
16
+ "max_backups": 7,
17
+ "max_age_days": 30,
18
+ "compress": true
19
+ },
20
+ "cli": {
21
+ "enabled": true,
22
+ "filename": "cli.log",
23
+ "max_size_mb": 100,
24
+ "max_backups": 7,
25
+ "max_age_days": 14,
26
+ "compress": true
27
+ },
28
+ "slow": {
29
+ "enabled": true,
30
+ "filename": "slow.log",
31
+ "max_size_mb": 100,
32
+ "max_backups": 7,
33
+ "max_age_days": 14,
34
+ "compress": true,
35
+ "threshold_ms": 1000
36
+ },
37
+ "environments": {
38
+ "development": {
39
+ "level": "DEBUG",
40
+ "slow": {
41
+ "threshold_ms": 300
42
+ }
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "enable_hmac": false,
3
+ "enable_rbac": true,
4
+ "enable_packet_encryption": false,
5
+ "packet_magic_len": 4,
6
+ "timestamp_skew_sec": 300,
7
+ "nonce_ttl_sec": 300,
8
+ "nonce_store": {
9
+ "driver": "redis",
10
+ "memcache_servers": ["localhost:11211"],
11
+ "redis_addr": "localhost:6379",
12
+ "redis_password": "",
13
+ "redis_db": 0,
14
+ "redis_prefix": "nonce:"
15
+ },
16
+ "environments": {
17
+ "production": {
18
+ "enable_packet_encryption": true
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "namespace": "entity-prod",
3
+ "default_email_domain": "example.com",
4
+ "language": "ko",
5
+ "environment": "production",
6
+ "port": 47200,
7
+ "enable_auto_schema_sync": true,
8
+ "global_license_scope": true,
9
+ "global_optimistic_lock": false
10
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "account_audit",
3
+ "description": "account_audit Entity",
4
+ "index": {
5
+ "account_seq": {
6
+ "comment": "계정 seq",
7
+ "type": "bigint",
8
+ "required": true
9
+ },
10
+ "action": {
11
+ "comment": "작업 유형",
12
+ "type": ["INSERT", "UPDATE", "DELETE", "LOGIN", "LOGOUT"],
13
+ "required": true
14
+ }
15
+ },
16
+ "license_scope": false
17
+ }
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "account",
3
+ "description": "JWT 로그인/인증 계정 예제",
4
+ "index": {
5
+ "user_seq": {
6
+ "comment": "사용자번호"
7
+ },
8
+ "email": {
9
+ "comment": "이메일",
10
+ "type": "email",
11
+ "required": true,
12
+ "unique": true
13
+ },
14
+ "status": {
15
+ "comment": "상태",
16
+ "type": ["active", "inactive", "blocked"],
17
+ "default": "active"
18
+ },
19
+ "rbac_role": {
20
+ "comment": "RBAC 역할 (JWT 인증/인가에 사용)",
21
+ "type": ["admin", "editor", "viewer", "auditor", "user"],
22
+ "default": "user"
23
+ }
24
+ },
25
+ "fk": {
26
+ "user_seq": "user.seq"
27
+ },
28
+ "reset_defaults": [
29
+ {
30
+ "email": "admin@example.com",
31
+ "passwd": "admin12345",
32
+ "rbac_role": "admin",
33
+ "status": "active"
34
+ }
35
+ ],
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
+ "after_insert": [
52
+ {
53
+ "type": "sql",
54
+ "query": "INSERT INTO account_audit (account_seq, action, email, created_time) VALUES (?, ?, ?, NOW())",
55
+ "params": ["${new.seq}", "INSERT", "${new.email}"],
56
+ "async": false
57
+ }
58
+ ]
59
+ }
60
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "api_keys",
3
+ "license_scope": false,
4
+ "description": "API 키 및 HMAC 시크릿 관리",
5
+ "index": {
6
+ "key_value": {
7
+ "comment": "API 키 값 (해시 저장)",
8
+ "required": true,
9
+ "unique": true,
10
+ "hash": true
11
+ },
12
+ "enabled": {
13
+ "comment": "활성 여부",
14
+ "type": "bool",
15
+ "default": true
16
+ },
17
+ "user_seq": {
18
+ "comment": "발급 대상 사용자 seq (nullable, user 엔티티 참조)"
19
+ }
20
+ },
21
+ "types": {
22
+ "hmac_secret": "text",
23
+ "entities": "text"
24
+ },
25
+ "hard_delete": true
26
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "license",
3
+ "description": "license Entity",
4
+ "index": {
5
+ "name": {
6
+ "comment": "라이선스명",
7
+ "required": true,
8
+ "unique": true
9
+ },
10
+ "contract_date": {
11
+ "comment": "계약일"
12
+ },
13
+ "expire_date": {
14
+ "comment": "만료일"
15
+ },
16
+ "status": {
17
+ "comment": "상태",
18
+ "type": ["active", "expired", "suspended"],
19
+ "default": "active"
20
+ }
21
+ },
22
+ "defaults": {
23
+ "max_users": 100
24
+ },
25
+ "types": {},
26
+ "reset_defaults": [
27
+ {
28
+ "name": "Trial License",
29
+ "contract_date": "2026-01-01",
30
+ "expire_date": "2026-12-31",
31
+ "status": "active",
32
+ "max_users": 10,
33
+ "contact_email": "trial@example.com"
34
+ }
35
+ ]
36
+ }