create-entity-server 0.0.26 → 0.0.31

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 (98) hide show
  1. package/package.json +1 -1
  2. package/template/.env.example +26 -0
  3. package/template/configs/auth/cors.json +15 -0
  4. package/template/configs/auth/jwt.json +12 -0
  5. package/template/configs/{oauth.json → auth/oauth.json} +7 -4
  6. package/template/configs/auth/password.json +45 -0
  7. package/template/configs/{security.json → auth/security.json} +4 -2
  8. package/template/configs/cache.json +2 -1
  9. package/template/configs/keys/.gitkeep +0 -0
  10. package/template/configs/keys/apns.p8.example +7 -0
  11. package/template/configs/keys/firebase.pem.example +7 -0
  12. package/template/configs/server.json +1 -0
  13. package/template/entities/README.md +13 -16
  14. package/template/entities/System/Auth/account.json +172 -16
  15. package/template/entities/System/Auth/account_audit.json +31 -0
  16. package/template/entities/System/Auth/account_device.json +64 -0
  17. package/template/entities/System/Auth/account_login_log.json +69 -0
  18. package/template/entities/System/Auth/account_oauth.json +74 -0
  19. package/template/entities/System/Auth/api_keys.json +18 -9
  20. package/template/entities/System/Auth/identity_verification.json +106 -0
  21. package/template/entities/System/Auth/license.json +41 -20
  22. package/template/entities/System/Auth/password_history.json +19 -0
  23. package/template/entities/System/Auth/rbac_roles.json +10 -7
  24. package/template/entities/System/Backup/backup_log.json +66 -0
  25. package/template/entities/System/Email/smtp_log.json +87 -0
  26. package/template/entities/System/Email/smtp_msg.json +104 -0
  27. package/template/entities/System/Notification/alimtalk_log.json +65 -0
  28. package/template/entities/System/Notification/alimtalk_msg.json +53 -0
  29. package/template/entities/System/Notification/friendtalk_log.json +89 -0
  30. package/template/entities/System/Notification/friendtalk_msg.json +91 -0
  31. package/template/entities/System/Notification/sms_log.json +65 -0
  32. package/template/entities/System/Notification/sms_msg.json +82 -0
  33. package/template/entities/System/Notification/sms_verification.json +50 -0
  34. package/template/entities/System/Payment/pg_cancel.json +60 -0
  35. package/template/entities/System/Payment/pg_order.json +115 -0
  36. package/template/entities/System/Payment/pg_webhook_log.json +52 -0
  37. package/template/entities/System/Push/push_log.json +86 -0
  38. package/template/entities/System/Push/push_msg.json +56 -0
  39. package/template/entities/System/Storage/file_backup_log.json +51 -0
  40. package/template/entities/System/Storage/file_download_log.json +43 -0
  41. package/template/entities/System/Storage/file_meta.json +72 -0
  42. package/template/entities/System/system_audit_log.json +39 -34
  43. package/template/entities/company.json +5 -2
  44. package/template/entities/{product.json → goods.json} +9 -6
  45. package/template/entities/todo.json +4 -2
  46. package/template/samples/entities/01_basic_fields.json +15 -2
  47. package/template/samples/entities/02_types_and_defaults.json +15 -5
  48. package/template/samples/entities/03_hash_and_unique.json +18 -3
  49. package/template/samples/entities/04_fk_and_composite_unique.json +18 -3
  50. package/template/samples/entities/05_cache.json +15 -9
  51. package/template/samples/entities/06_history_and_hard_delete.json +19 -6
  52. package/template/samples/entities/07_license_scope.json +18 -3
  53. package/template/samples/entities/08_hook_sql.json +24 -5
  54. package/template/samples/entities/09_hook_entity.json +12 -2
  55. package/template/samples/entities/10_hook_submit_delete.json +14 -5
  56. package/template/samples/entities/11_hook_webhook.json +20 -6
  57. package/template/samples/entities/12_hook_push.json +15 -2
  58. package/template/samples/entities/13_read_only.json +8 -4
  59. package/template/samples/entities/14_optimistic_lock.json +13 -2
  60. package/template/samples/entities/15_reset_defaults.json +7 -1
  61. package/template/samples/entities/16_isolated_license.json +19 -6
  62. package/template/scripts/reset-all.sh +57 -3
  63. package/template/scripts/run.sh +56 -6
  64. package/template/templates/email/auth/2fa_disabled.html +23 -0
  65. package/template/templates/email/auth/2fa_recovery_regenerated.html +31 -0
  66. package/template/templates/email/auth/2fa_setup_complete.html +43 -0
  67. package/template/templates/email/auth/welcome.html +18 -0
  68. package/template/templates/email/backup/backup_completed.html +35 -0
  69. package/template/templates/email/backup/backup_failed.html +27 -0
  70. package/template/templates/email/backup/backup_partial.html +31 -0
  71. package/template/templates/email/layout.html +47 -0
  72. package/template/templates/email/order/order_confirmation.html +30 -0
  73. package/template/templates/email/storage/storage_quota_exceeded.html +37 -0
  74. package/template/templates/email/storage/storage_quota_warning.html +37 -0
  75. package/template/templates/ocr/business_reg.json +145 -0
  76. package/template/templates/ocr/career_cert.json +93 -0
  77. package/template/templates/ocr/driver_license.json +89 -0
  78. package/template/templates/ocr/facility_card.json +82 -0
  79. package/template/templates/ocr/id_card.json +55 -0
  80. package/template/templates/ocr/invoice.json +92 -0
  81. package/template/templates/ocr/namecard.json +116 -0
  82. package/template/templates/ocr/prompts/business_reg.json +14 -0
  83. package/template/templates/ocr/prompts/career_cert.json +16 -0
  84. package/template/templates/ocr/prompts/driver_license.json +14 -0
  85. package/template/templates/ocr/prompts/facility_card.json +15 -0
  86. package/template/templates/ocr/prompts/general.json +13 -0
  87. package/template/templates/ocr/prompts/id_card.json +11 -0
  88. package/template/templates/ocr/prompts/invoice.json +17 -0
  89. package/template/templates/ocr/prompts/namecard.json +15 -0
  90. package/template/templates/ocr/prompts/receipt.json +14 -0
  91. package/template/templates/ocr/receipt.json +79 -0
  92. package/template/configs/cors.json +0 -7
  93. package/template/configs/jwt.json +0 -8
  94. package/template/entities/Account/account_audit.json +0 -16
  95. /package/template/configs/{backup.json → extensions/backup.json} +0 -0
  96. /package/template/configs/{storage.json → extensions/storage.json} +0 -0
  97. /package/template/configs/{push.json → notification/push.json} +0 -0
  98. /package/template/configs/{smtp.json → notification/smtp.json} +0 -0
@@ -1,15 +1,27 @@
1
1
  #!/bin/bash
2
2
 
3
+ set -e
4
+ set -o pipefail
5
+
3
6
  # Reset all entity tables and seed default data
4
7
 
5
8
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
9
  PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
10
+ RESET_LOG=""
11
+
12
+ cleanup() {
13
+ if [ -n "$RESET_LOG" ] && [ -f "$RESET_LOG" ]; then
14
+ rm -f "$RESET_LOG"
15
+ fi
16
+ }
17
+
18
+ trap cleanup EXIT
7
19
 
8
20
  cd "$PROJECT_ROOT"
9
21
 
10
22
  # Load language from .env
11
23
  if [ -f .env ]; then
12
- LANGUAGE=$(grep '^LANGUAGE=' .env | cut -d '=' -f2)
24
+ LANGUAGE=$(grep '^LANGUAGE=' .env | cut -d '=' -f2 || true)
13
25
  fi
14
26
  LANGUAGE=${LANGUAGE:-ko}
15
27
 
@@ -76,10 +88,52 @@ case "$1" in
76
88
  echo "⚙️ 필수 엔티티 확인 중..."
77
89
  fi
78
90
  LANGUAGE="$LANGUAGE" "$SCRIPT_DIR/normalize-entities.sh" --apply
91
+ RESET_LOG=$(mktemp)
79
92
  if [ "$1" = "--force" ]; then
80
- "$PROJECT_ROOT/bin/entity-cli" reset-all --apply --force
93
+ set +e
94
+ "$PROJECT_ROOT/bin/entity-cli" reset-all --apply --force 2>&1 | tee "$RESET_LOG"
95
+ RESET_STATUS=${PIPESTATUS[0]}
96
+ set -e
81
97
  else
82
- "$PROJECT_ROOT/bin/entity-cli" reset-all --apply
98
+ set +e
99
+ "$PROJECT_ROOT/bin/entity-cli" reset-all --apply 2>&1 | tee "$RESET_LOG"
100
+ RESET_STATUS=${PIPESTATUS[0]}
101
+ set -e
102
+ fi
103
+
104
+ if [ "$RESET_STATUS" -ne 0 ]; then
105
+ exit "$RESET_STATUS"
106
+ fi
107
+
108
+ if ! grep -q "Reset completed successfully!" "$RESET_LOG"; then
109
+ exit 0
110
+ fi
111
+
112
+ # 새로 생성된 API 키 조회 후 .env 업데이트
113
+ NEW_API_KEY=$("$PROJECT_ROOT/bin/entity-cli" api-key show --seq=1 --reveal-secret 2>/dev/null | grep -E '^key_value' | awk '{print $NF}')
114
+ NEW_HMAC=$("$PROJECT_ROOT/bin/entity-cli" api-key show --seq=1 --reveal-secret 2>/dev/null | grep -E '^hmac_secret' | awk '{print $NF}')
115
+
116
+ if [ -n "$NEW_API_KEY" ] && [ -n "$NEW_HMAC" ]; then
117
+ echo ""
118
+ if [ "$LANGUAGE" = "en" ]; then
119
+ echo "🔑 New API Key generated:"
120
+ else
121
+ echo "🔑 새 API 키가 생성되었습니다:"
122
+ fi
123
+ echo " ENTITY_API_KEY = $NEW_API_KEY"
124
+ echo " ENTITY_HMAC_SECRET = $NEW_HMAC"
125
+
126
+ # 게이트웨이 .env 자동 업데이트 (같은 저장소 내에 있는 경우)
127
+ GW_ENV="$PROJECT_ROOT/packages/entity-app-server/.env"
128
+ if [ -f "$GW_ENV" ]; then
129
+ sed -i "s|^ENTITY_API_KEY=.*|ENTITY_API_KEY=$NEW_API_KEY|" "$GW_ENV"
130
+ sed -i "s|^ENTITY_HMAC_SECRET=.*|ENTITY_HMAC_SECRET=$NEW_HMAC|" "$GW_ENV"
131
+ if [ "$LANGUAGE" = "en" ]; then
132
+ echo " ✓ Updated: packages/entity-app-server/.env"
133
+ else
134
+ echo " ✓ 업데이트 완료: packages/entity-app-server/.env"
135
+ fi
136
+ fi
83
137
  fi
84
138
  ;;
85
139
  *)
@@ -62,6 +62,9 @@ is_running() {
62
62
  if [ -n "$port_pid" ] && kill -0 "$port_pid" 2>/dev/null; then
63
63
  return 0
64
64
  fi
65
+ if is_port_in_use; then
66
+ return 0
67
+ fi
65
68
  return 1
66
69
  fi
67
70
  local pid
@@ -70,6 +73,9 @@ is_running() {
70
73
  if [ -n "$port_pid" ] && kill -0 "$port_pid" 2>/dev/null; then
71
74
  return 0
72
75
  fi
76
+ if is_port_in_use; then
77
+ return 0
78
+ fi
73
79
  return 1
74
80
  fi
75
81
  if kill -0 "$pid" 2>/dev/null; then
@@ -80,6 +86,10 @@ is_running() {
80
86
  return 0
81
87
  fi
82
88
 
89
+ if is_port_in_use; then
90
+ return 0
91
+ fi
92
+
83
93
  return 1
84
94
  }
85
95
 
@@ -89,6 +99,26 @@ find_pid_by_port() {
89
99
  ss -ltnp 2>/dev/null | grep ":$port " | sed -n 's/.*pid=\([0-9]\+\).*/\1/p' | head -n 1
90
100
  }
91
101
 
102
+ is_port_in_use() {
103
+ local port
104
+ port=$(get_server_value "port" "3400")
105
+ ss -ltn 2>/dev/null | grep -q ":$port "
106
+ }
107
+
108
+ show_port_in_use_message() {
109
+ local port
110
+ port=$(get_server_value "port" "3400")
111
+ if [ "$LANGUAGE" = "en" ]; then
112
+ echo "❌ Port $port is already in use, but the owning PID could not be identified."
113
+ echo "Check with: ss -ltnp | grep :$port"
114
+ echo "Or: lsof -iTCP:$port -sTCP:LISTEN -n -P"
115
+ else
116
+ echo "❌ 포트 $port 가 이미 사용 중이지만, 점유 PID를 식별할 수 없습니다."
117
+ echo "확인: ss -ltnp | grep :$port"
118
+ echo "또는: lsof -iTCP:$port -sTCP:LISTEN -n -P"
119
+ fi
120
+ }
121
+
92
122
  stop_pid_with_confirm() {
93
123
  local pid="$1"
94
124
  local reason="$2"
@@ -157,6 +187,10 @@ stop_server() {
157
187
  stop_pid_with_confirm "$port_pid" "port fallback"
158
188
  return $?
159
189
  fi
190
+ if is_port_in_use; then
191
+ show_port_in_use_message
192
+ return 1
193
+ fi
160
194
  if [ "$LANGUAGE" = "en" ]; then
161
195
  echo "ℹ️ Server is not running (pid file not found)."
162
196
  else
@@ -191,6 +225,10 @@ stop_server() {
191
225
  stop_pid_with_confirm "$port_pid" "port fallback"
192
226
  return $?
193
227
  fi
228
+ if is_port_in_use; then
229
+ show_port_in_use_message
230
+ return 1
231
+ fi
194
232
  if [ "$LANGUAGE" = "en" ]; then
195
233
  echo "ℹ️ Stale pid file removed (process not found)."
196
234
  else
@@ -305,10 +343,16 @@ fi
305
343
  case "$MODE" in
306
344
  dev|development)
307
345
  if is_running; then
308
- if [ "$LANGUAGE" = "en" ]; then
309
- echo "❌ Server already running (pid: $(cat "$PID_FILE")). Stop first: ./run.sh stop"
346
+ local running_pid
347
+ running_pid=$(find_pid_by_port)
348
+ if [ -n "$running_pid" ]; then
349
+ if [ "$LANGUAGE" = "en" ]; then
350
+ echo "❌ Server already running (pid: $running_pid). Stop first: ./run.sh stop"
351
+ else
352
+ echo "❌ 이미 서버가 실행 중입니다 (pid: $running_pid). 먼저 중지하세요: ./run.sh stop"
353
+ fi
310
354
  else
311
- echo "❌ 이미 서버가 실행 중입니다 (pid: $(cat "$PID_FILE")). 먼저 중지하세요: ./run.sh stop"
355
+ show_port_in_use_message
312
356
  fi
313
357
  exit 1
314
358
  fi
@@ -329,10 +373,16 @@ case "$MODE" in
329
373
 
330
374
  start)
331
375
  if is_running; then
332
- if [ "$LANGUAGE" = "en" ]; then
333
- echo "❌ Server already running (pid: $(cat "$PID_FILE")). Stop first: ./run.sh stop"
376
+ local running_pid
377
+ running_pid=$(find_pid_by_port)
378
+ if [ -n "$running_pid" ]; then
379
+ if [ "$LANGUAGE" = "en" ]; then
380
+ echo "❌ Server already running (pid: $running_pid). Stop first: ./run.sh stop"
381
+ else
382
+ echo "❌ 이미 서버가 실행 중입니다 (pid: $running_pid). 먼저 중지하세요: ./run.sh stop"
383
+ fi
334
384
  else
335
- echo "❌ 이미 서버가 실행 중입니다 (pid: $(cat "$PID_FILE")). 먼저 중지하세요: ./run.sh stop"
385
+ show_port_in_use_message
336
386
  fi
337
387
  exit 1
338
388
  fi
@@ -0,0 +1,23 @@
1
+ <h2 style="margin: 0 0 16px; font-size: 22px; font-weight: 700; color: #1a1a2e;">2단계 인증이 비활성화되었습니다</h2>
2
+ <p style="margin: 0 0 16px; font-size: 15px; color: #555; line-height: 1.6;">
3
+ <strong>${email}</strong> 계정의 2단계 인증(TOTP)이 해제되었습니다.<br>
4
+ 앞으로 이메일과 비밀번호만으로 로그인됩니다.
5
+ </p>
6
+
7
+ <div style="margin: 0 0 24px; padding: 20px; background-color: #f8d7da; border-radius: 8px; border-left: 4px solid #dc3545;">
8
+ <p style="margin: 0; font-size: 14px; color: #721c24; line-height: 1.6;">
9
+ <strong>보안 알림:</strong> 2단계 인증이 해제되면 계정 보안 수준이 낮아집니다.<br>
10
+ 가능하면 다시 2단계 인증을 설정하는 것을 권장합니다.
11
+ </p>
12
+ </div>
13
+
14
+ <p style="margin: 0 0 8px; font-size: 14px; color: #888; line-height: 1.6;">
15
+ 해제 시각: ${disabled_time}
16
+ </p>
17
+ <p style="margin: 0 0 8px; font-size: 14px; color: #888; line-height: 1.6;">
18
+ 해제 방법: ${disabled_by|본인 요청}
19
+ </p>
20
+
21
+ <p style="margin: 0; font-size: 13px; color: #aaa; line-height: 1.6;">
22
+ 본인이 해제하지 않은 경우, 즉시 비밀번호를 변경하고 관리자에게 문의하세요.
23
+ </p>
@@ -0,0 +1,31 @@
1
+ <h2 style="margin: 0 0 16px; font-size: 22px; font-weight: 700; color: #1a1a2e;">복구 코드가 재생성되었습니다</h2>
2
+ <p style="margin: 0 0 16px; font-size: 15px; color: #555; line-height: 1.6;">
3
+ <strong>${email}</strong> 계정의 2단계 인증 복구 코드가 새로 생성되었습니다.<br>
4
+ 이전 복구 코드는 모두 <strong>폐기</strong>되었습니다.
5
+ </p>
6
+
7
+ <div style="margin: 0 0 24px; padding: 20px; background-color: #fff3cd; border-radius: 8px; border-left: 4px solid #ffc107;">
8
+ <p style="margin: 0 0 8px; font-size: 14px; font-weight: 700; color: #856404;">⚠ 새 복구 코드를 안전한 곳에 저장하세요</p>
9
+ <p style="margin: 0; font-size: 13px; color: #856404; line-height: 1.5;">
10
+ 각 코드는 <strong>한 번만</strong> 사용할 수 있으며, 이 이메일 이후에는 다시 확인할 수 없습니다.
11
+ </p>
12
+ </div>
13
+
14
+ <div style="text-align: center; margin: 0 0 24px;">
15
+ <table role="presentation" cellpadding="0" cellspacing="0" style="margin: 0 auto;">
16
+ <tr>
17
+ <td style="padding: 16px 32px; background-color: #f0f0f5; border-radius: 8px;">
18
+ <pre style="margin: 0; font-size: 18px; font-weight: 700; color: #1a1a2e; font-family: 'Courier New', monospace; letter-spacing: 2px; line-height: 2;">${recovery_codes}</pre>
19
+ </td>
20
+ </tr>
21
+ </table>
22
+ </div>
23
+
24
+ <p style="margin: 0 0 16px; font-size: 14px; color: #888; line-height: 1.6;">
25
+ 새 복구 코드: <strong>${recovery_count|10}개</strong><br>
26
+ 재생성 시각: ${regenerated_time}
27
+ </p>
28
+
29
+ <p style="margin: 0; font-size: 13px; color: #aaa; line-height: 1.6;">
30
+ 본인이 요청하지 않은 경우, 계정이 보안 위협에 노출되었을 수 있습니다. 즉시 비밀번호를 변경하고 관리자에게 문의하세요.
31
+ </p>
@@ -0,0 +1,43 @@
1
+ <h2 style="margin: 0 0 16px; font-size: 22px; font-weight: 700; color: #1a1a2e;">2단계 인증이 활성화되었습니다</h2>
2
+ <p style="margin: 0 0 16px; font-size: 15px; color: #555; line-height: 1.6;">
3
+ <strong>${email}</strong> 계정에 2단계 인증(TOTP)이 설정되었습니다.<br>
4
+ 앞으로 로그인 시 Authenticator 앱의 인증 코드가 필요합니다.
5
+ </p>
6
+
7
+ <div style="margin: 0 0 24px; padding: 20px; background-color: #fff3cd; border-radius: 8px; border-left: 4px solid #ffc107;">
8
+ <p style="margin: 0 0 8px; font-size: 14px; font-weight: 700; color: #856404;">⚠ 복구 코드를 안전한 곳에 저장하세요</p>
9
+ <p style="margin: 0 0 12px; font-size: 13px; color: #856404; line-height: 1.5;">
10
+ Authenticator 앱에 접근할 수 없는 경우 아래 복구 코드로 로그인할 수 있습니다.<br>
11
+ 각 코드는 <strong>한 번만</strong> 사용할 수 있으며, 이 이메일 이후에는 다시 확인할 수 없습니다.
12
+ </p>
13
+ </div>
14
+
15
+ <div style="text-align: center; margin: 0 0 24px;">
16
+ <table role="presentation" cellpadding="0" cellspacing="0" style="margin: 0 auto;">
17
+ <tr>
18
+ <td style="padding: 16px 32px; background-color: #f0f0f5; border-radius: 8px;">
19
+ <pre style="margin: 0; font-size: 18px; font-weight: 700; color: #1a1a2e; font-family: 'Courier New', monospace; letter-spacing: 2px; line-height: 2;">${recovery_codes}</pre>
20
+ </td>
21
+ </tr>
22
+ </table>
23
+ </div>
24
+
25
+ <p style="margin: 0 0 8px; font-size: 14px; color: #888; line-height: 1.6;">
26
+ 남은 복구 코드: <strong>${recovery_count|10}개</strong>
27
+ </p>
28
+ <p style="margin: 0 0 16px; font-size: 14px; color: #888; line-height: 1.6;">
29
+ 설정 시각: ${enabled_time}
30
+ </p>
31
+
32
+ <div style="margin: 0 0 16px; padding: 16px; background-color: #f8f9fa; border-radius: 6px;">
33
+ <p style="margin: 0; font-size: 13px; color: #666; line-height: 1.6;">
34
+ 💡 <strong>권장 사항</strong><br>
35
+ • 복구 코드를 출력하거나 비밀번호 관리자에 저장하세요<br>
36
+ • 복구 코드가 3개 이하로 줄면 재생성하세요<br>
37
+ • 본인이 설정하지 않았다면 즉시 비밀번호를 변경하세요
38
+ </p>
39
+ </div>
40
+
41
+ <p style="margin: 0; font-size: 13px; color: #aaa; line-height: 1.6;">
42
+ 본인이 설정하지 않은 경우, 계정이 보안 위협에 노출되었을 수 있습니다. 즉시 비밀번호를 변경하고 관리자에게 문의하세요.
43
+ </p>
@@ -0,0 +1,18 @@
1
+ <h2 style="margin: 0 0 16px; font-size: 22px; font-weight: 700; color: #1a1a2e;">환영합니다, ${name|회원}님!</h2>
2
+ <p style="margin: 0 0 24px; font-size: 15px; color: #555; line-height: 1.6;">
3
+ 회원가입이 완료되었습니다. ${app_name|서비스}를 이용해 주셔서 감사합니다.
4
+ </p>
5
+ <div style="text-align: center; margin: 0 0 24px;">
6
+ <a href="${login_url|#}" style="display: inline-block; padding: 14px 36px; background-color: #1a1a2e; color: #ffffff; text-decoration: none; border-radius: 6px; font-size: 15px; font-weight: 600;">시작하기</a>
7
+ </div>
8
+ <div style="padding: 20px; background-color: #f8f8fa; border-radius: 6px; margin: 0 0 24px;">
9
+ <p style="margin: 0 0 8px; font-size: 14px; font-weight: 600; color: #1a1a2e;">계정 정보</p>
10
+ <p style="margin: 0; font-size: 14px; color: #555; line-height: 1.8;">
11
+ 이메일: ${email|user@example.com}<br>
12
+ 아이디: ${account_id|user001}<br>
13
+ 가입일: ${created_at|오늘}
14
+ </p>
15
+ </div>
16
+ <p style="margin: 0; font-size: 13px; color: #aaa; line-height: 1.6;">
17
+ 궁금한 점이 있으시면 언제든 문의해 주세요.
18
+ </p>
@@ -0,0 +1,35 @@
1
+ <h2 style="margin: 0 0 16px; font-size: 22px; font-weight: 700; color: #27ae60;">✅ 백업 완료</h2>
2
+ <p style="margin: 0 0 24px; font-size: 15px; color: #555; line-height: 1.6;">
3
+ 데이터 백업이 정상적으로 완료되었습니다.
4
+ </p>
5
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin: 0 0 24px;">
6
+ <tr>
7
+ <td style="padding: 16px 20px; background-color: #eafaf1; border-radius: 6px; border-left: 4px solid #27ae60;">
8
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0">
9
+ <tr>
10
+ <td style="padding: 4px 0; font-size: 14px; color: #555; width: 120px; font-weight: 600;">세션 ID</td>
11
+ <td style="padding: 4px 0; font-size: 14px; color: #333;">${session_id|—}</td>
12
+ </tr>
13
+ <tr>
14
+ <td style="padding: 4px 0; font-size: 14px; color: #555; width: 120px; font-weight: 600;">상태</td>
15
+ <td style="padding: 4px 0; font-size: 14px; font-weight: 700; color: #27ae60;">완료</td>
16
+ </tr>
17
+ <tr>
18
+ <td style="padding: 4px 0; font-size: 14px; color: #555; width: 120px; font-weight: 600;">레코드 수</td>
19
+ <td style="padding: 4px 0; font-size: 14px; color: #333;">${total_records|0}건</td>
20
+ </tr>
21
+ <tr>
22
+ <td style="padding: 4px 0; font-size: 14px; color: #555; width: 120px; font-weight: 600;">백업 크기</td>
23
+ <td style="padding: 4px 0; font-size: 14px; color: #333;">${total_size|0 B}</td>
24
+ </tr>
25
+ <tr>
26
+ <td style="padding: 4px 0; font-size: 14px; color: #555; width: 120px; font-weight: 600;">소요 시간</td>
27
+ <td style="padding: 4px 0; font-size: 14px; color: #333;">${elapsed|—}</td>
28
+ </tr>
29
+ </table>
30
+ </td>
31
+ </tr>
32
+ </table>
33
+ <p style="margin: 0; font-size: 13px; color: #aaa; line-height: 1.6;">
34
+ 이 알림은 백업 완료 시 자동 발송됩니다.
35
+ </p>
@@ -0,0 +1,27 @@
1
+ <h2 style="margin: 0 0 16px; font-size: 22px; font-weight: 700; color: #e74c3c;">❌ 백업 실패</h2>
2
+ <p style="margin: 0 0 24px; font-size: 15px; color: #555; line-height: 1.6;">
3
+ 데이터 백업에 실패했습니다. 오류 내용을 확인하고 조치해 주세요.
4
+ </p>
5
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin: 0 0 24px;">
6
+ <tr>
7
+ <td style="padding: 16px 20px; background-color: #fdedec; border-radius: 6px; border-left: 4px solid #e74c3c;">
8
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0">
9
+ <tr>
10
+ <td style="padding: 4px 0; font-size: 14px; color: #555; width: 120px; font-weight: 600;">세션 ID</td>
11
+ <td style="padding: 4px 0; font-size: 14px; color: #333;">${session_id|—}</td>
12
+ </tr>
13
+ <tr>
14
+ <td style="padding: 4px 0; font-size: 14px; color: #555; width: 120px; font-weight: 600;">상태</td>
15
+ <td style="padding: 4px 0; font-size: 14px; font-weight: 700; color: #e74c3c;">실패</td>
16
+ </tr>
17
+ </table>
18
+ </td>
19
+ </tr>
20
+ </table>
21
+ <div style="margin: 0 0 24px;">
22
+ <p style="margin: 0 0 8px; font-size: 14px; font-weight: 600; color: #1a1a2e;">오류 내용</p>
23
+ <pre style="margin: 0; padding: 12px 16px; background-color: #f8f8fa; border-radius: 6px; font-size: 13px; color: #e74c3c; line-height: 1.6; white-space: pre-wrap; word-break: break-all;">${error_message|알 수 없는 오류}</pre>
24
+ </div>
25
+ <p style="margin: 0; font-size: 13px; color: #aaa; line-height: 1.6;">
26
+ 이 알림은 백업 실패 시 자동 발송됩니다.
27
+ </p>
@@ -0,0 +1,31 @@
1
+ <h2 style="margin: 0 0 16px; font-size: 22px; font-weight: 700; color: #e67e22;">⚠️ 백업 부분 완료</h2>
2
+ <p style="margin: 0 0 24px; font-size: 15px; color: #555; line-height: 1.6;">
3
+ 백업이 완료되었으나 일부 엔티티에서 오류가 발생했습니다. 실패한 항목을 확인해 주세요.
4
+ </p>
5
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin: 0 0 24px;">
6
+ <tr>
7
+ <td style="padding: 16px 20px; background-color: #fef9e7; border-radius: 6px; border-left: 4px solid #e67e22;">
8
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0">
9
+ <tr>
10
+ <td style="padding: 4px 0; font-size: 14px; color: #555; width: 120px; font-weight: 600;">세션 ID</td>
11
+ <td style="padding: 4px 0; font-size: 14px; color: #333;">${session_id|—}</td>
12
+ </tr>
13
+ <tr>
14
+ <td style="padding: 4px 0; font-size: 14px; color: #555; width: 120px; font-weight: 600;">상태</td>
15
+ <td style="padding: 4px 0; font-size: 14px; font-weight: 700; color: #e67e22;">부분 완료</td>
16
+ </tr>
17
+ <tr>
18
+ <td style="padding: 4px 0; font-size: 14px; color: #555; width: 120px; font-weight: 600;">레코드 수</td>
19
+ <td style="padding: 4px 0; font-size: 14px; color: #333;">${total_records|0}건</td>
20
+ </tr>
21
+ </table>
22
+ </td>
23
+ </tr>
24
+ </table>
25
+ <div style="margin: 0 0 24px;">
26
+ <p style="margin: 0 0 8px; font-size: 14px; font-weight: 600; color: #1a1a2e;">실패한 엔티티</p>
27
+ <pre style="margin: 0; padding: 12px 16px; background-color: #f8f8fa; border-radius: 6px; font-size: 13px; color: #555; line-height: 1.6; white-space: pre-wrap; word-break: break-all;">${failed_entities|없음}</pre>
28
+ </div>
29
+ <p style="margin: 0; font-size: 13px; color: #aaa; line-height: 1.6;">
30
+ 이 알림은 백업 결과에 오류가 포함되었을 때 자동 발송됩니다.
31
+ </p>
@@ -0,0 +1,47 @@
1
+ <!DOCTYPE html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <!--[if mso]>
7
+ <noscript>
8
+ <xml>
9
+ <o:OfficeDocumentSettings>
10
+ <o:PixelsPerInch>96</o:PixelsPerInch>
11
+ </o:OfficeDocumentSettings>
12
+ </xml>
13
+ </noscript>
14
+ <![endif]-->
15
+ </head>
16
+ <body style="margin: 0; padding: 0; background-color: #f4f4f7; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%;">
17
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color: #f4f4f7;">
18
+ <tr>
19
+ <td align="center" style="padding: 24px 16px;">
20
+ <table role="presentation" width="600" cellpadding="0" cellspacing="0" style="max-width: 600px; width: 100%; background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.08);">
21
+ <!-- Header -->
22
+ <tr>
23
+ <td style="padding: 32px 40px 24px; border-bottom: 1px solid #eaeaea;">
24
+ <span style="font-size: 18px; font-weight: 700; color: #1a1a2e;">${app_name|Entity Server}</span>
25
+ </td>
26
+ </tr>
27
+ <!-- Content -->
28
+ <tr>
29
+ <td style="padding: 32px 40px;">
30
+ ${content}
31
+ </td>
32
+ </tr>
33
+ <!-- Footer -->
34
+ <tr>
35
+ <td style="padding: 24px 40px; border-top: 1px solid #eaeaea; background-color: #fafafa;">
36
+ <p style="margin: 0; font-size: 12px; color: #999; line-height: 1.6;">
37
+ 이 이메일은 자동 발송되었습니다. 회신하지 마세요.<br>
38
+ &copy; ${company|Entity Server}
39
+ </p>
40
+ </td>
41
+ </tr>
42
+ </table>
43
+ </td>
44
+ </tr>
45
+ </table>
46
+ </body>
47
+ </html>
@@ -0,0 +1,30 @@
1
+ <h2 style="margin: 0 0 16px; font-size: 22px; font-weight: 700; color: #1a1a2e;">주문이 확인되었습니다</h2>
2
+ <p style="margin: 0 0 24px; font-size: 15px; color: #555; line-height: 1.6;">
3
+ ${name|고객}님, 주문해 주셔서 감사합니다.
4
+ </p>
5
+ <div style="padding: 20px; background-color: #f8f8fa; border-radius: 6px; margin: 0 0 24px;">
6
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0">
7
+ <tr>
8
+ <td style="padding: 8px 0; font-size: 14px; color: #888;">주문번호</td>
9
+ <td style="padding: 8px 0; font-size: 14px; color: #1a1a2e; font-weight: 600; text-align: right;">${order_id|ORD-000001}</td>
10
+ </tr>
11
+ <tr>
12
+ <td style="padding: 8px 0; font-size: 14px; color: #888; border-top: 1px solid #eaeaea;">주문일시</td>
13
+ <td style="padding: 8px 0; font-size: 14px; color: #1a1a2e; text-align: right; border-top: 1px solid #eaeaea;">${order_date|2026-01-01}</td>
14
+ </tr>
15
+ <tr>
16
+ <td style="padding: 8px 0; font-size: 14px; color: #888; border-top: 1px solid #eaeaea;">상품</td>
17
+ <td style="padding: 8px 0; font-size: 14px; color: #1a1a2e; text-align: right; border-top: 1px solid #eaeaea;">${items|상품 1건}</td>
18
+ </tr>
19
+ <tr>
20
+ <td style="padding: 12px 0; font-size: 16px; font-weight: 700; color: #1a1a2e; border-top: 2px solid #1a1a2e;">총 결제금액</td>
21
+ <td style="padding: 12px 0; font-size: 16px; font-weight: 700; color: #1a1a2e; text-align: right; border-top: 2px solid #1a1a2e;">${total|50,000원}</td>
22
+ </tr>
23
+ </table>
24
+ </div>
25
+ <div style="text-align: center; margin: 0 0 24px;">
26
+ <a href="${detail_url|#}" style="display: inline-block; padding: 14px 36px; background-color: #1a1a2e; color: #ffffff; text-decoration: none; border-radius: 6px; font-size: 15px; font-weight: 600;">주문 상세 보기</a>
27
+ </div>
28
+ <p style="margin: 0; font-size: 13px; color: #aaa; line-height: 1.6;">
29
+ 주문 관련 문의사항이 있으시면 고객센터로 연락해 주세요.
30
+ </p>
@@ -0,0 +1,37 @@
1
+ <h2 style="margin: 0 0 16px; font-size: 22px; font-weight: 700; color: #e74c3c;">🚫 저장공간 초과</h2>
2
+ <p style="margin: 0 0 24px; font-size: 15px; color: #555; line-height: 1.6;">
3
+ 저장공간 사용량이 제한 용량을 초과했습니다. 파일 업로드가 차단됩니다. 즉시 불필요한 파일을 삭제하거나 쿼터를 늘려주세요.
4
+ </p>
5
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin: 0 0 24px;">
6
+ <tr>
7
+ <td style="padding: 16px 20px; background-color: #fdedec; border-radius: 6px; border-left: 4px solid #e74c3c;">
8
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0">
9
+ <tr>
10
+ <td style="padding: 4px 0; font-size: 14px; color: #555; width: 120px; font-weight: 600;">사용량</td>
11
+ <td style="padding: 4px 0; font-size: 14px; color: #333;">${used|0 B}</td>
12
+ </tr>
13
+ <tr>
14
+ <td style="padding: 4px 0; font-size: 14px; color: #555; width: 120px; font-weight: 600;">제한 용량</td>
15
+ <td style="padding: 4px 0; font-size: 14px; color: #333;">${limit|0 B}</td>
16
+ </tr>
17
+ <tr>
18
+ <td style="padding: 4px 0; font-size: 14px; color: #555; width: 120px; font-weight: 600;">사용률</td>
19
+ <td style="padding: 4px 0; font-size: 15px; font-weight: 700; color: #e74c3c;">${percent|0}%</td>
20
+ </tr>
21
+ <tr>
22
+ <td style="padding: 4px 0; font-size: 14px; color: #555; width: 120px; font-weight: 600;">업로드 시도</td>
23
+ <td style="padding: 4px 0; font-size: 14px; color: #333;">${upload_size|0 B}</td>
24
+ </tr>
25
+ </table>
26
+ </td>
27
+ </tr>
28
+ </table>
29
+ <!-- 사용률 바 (100%) -->
30
+ <div style="margin: 0 0 24px;">
31
+ <div style="background-color: #eee; border-radius: 4px; height: 12px; overflow: hidden;">
32
+ <div style="background-color: #e74c3c; height: 100%; width: 100%; border-radius: 4px;"></div>
33
+ </div>
34
+ </div>
35
+ <p style="margin: 0; font-size: 13px; color: #aaa; line-height: 1.6;">
36
+ 이 알림은 저장공간 사용량이 제한 용량을 초과했을 때 자동 발송됩니다.
37
+ </p>
@@ -0,0 +1,37 @@
1
+ <h2 style="margin: 0 0 16px; font-size: 22px; font-weight: 700; color: #e67e22;">⚠️ 저장공간 사용량 경고</h2>
2
+ <p style="margin: 0 0 24px; font-size: 15px; color: #555; line-height: 1.6;">
3
+ 저장공간 사용량이 경고 임계값에 도달했습니다. 오래된 파일을 정리하거나 쿼터를 늘려주세요.
4
+ </p>
5
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin: 0 0 24px;">
6
+ <tr>
7
+ <td style="padding: 16px 20px; background-color: #fef9e7; border-radius: 6px; border-left: 4px solid #e67e22;">
8
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0">
9
+ <tr>
10
+ <td style="padding: 4px 0; font-size: 14px; color: #555; width: 120px; font-weight: 600;">사용량</td>
11
+ <td style="padding: 4px 0; font-size: 14px; color: #333;">${used|0 B}</td>
12
+ </tr>
13
+ <tr>
14
+ <td style="padding: 4px 0; font-size: 14px; color: #555; width: 120px; font-weight: 600;">제한 용량</td>
15
+ <td style="padding: 4px 0; font-size: 14px; color: #333;">${limit|0 B}</td>
16
+ </tr>
17
+ <tr>
18
+ <td style="padding: 4px 0; font-size: 14px; color: #555; width: 120px; font-weight: 600;">사용률</td>
19
+ <td style="padding: 4px 0; font-size: 15px; font-weight: 700; color: #e67e22;">${percent|0}%</td>
20
+ </tr>
21
+ <tr>
22
+ <td style="padding: 4px 0; font-size: 14px; color: #555; width: 120px; font-weight: 600;">임계값</td>
23
+ <td style="padding: 4px 0; font-size: 14px; color: #333;">${threshold|80}%</td>
24
+ </tr>
25
+ </table>
26
+ </td>
27
+ </tr>
28
+ </table>
29
+ <!-- 사용률 바 -->
30
+ <div style="margin: 0 0 24px;">
31
+ <div style="background-color: #eee; border-radius: 4px; height: 12px; overflow: hidden;">
32
+ <div style="--quota-percent: ${percent|0}%; background-color: #e67e22; height: 100%; width: var(--quota-percent); max-width: 100%; border-radius: 4px;"></div>
33
+ </div>
34
+ </div>
35
+ <p style="margin: 0; font-size: 13px; color: #aaa; line-height: 1.6;">
36
+ 이 알림은 저장공간 사용량이 임계값(${threshold|80}%)을 초과했을 때 자동 발송됩니다.
37
+ </p>