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.
- package/package.json +1 -1
- package/template/.env.example +26 -0
- package/template/configs/auth/cors.json +15 -0
- package/template/configs/auth/jwt.json +12 -0
- package/template/configs/{oauth.json → auth/oauth.json} +7 -4
- package/template/configs/auth/password.json +45 -0
- package/template/configs/{security.json → auth/security.json} +4 -2
- package/template/configs/cache.json +2 -1
- package/template/configs/keys/.gitkeep +0 -0
- package/template/configs/keys/apns.p8.example +7 -0
- package/template/configs/keys/firebase.pem.example +7 -0
- package/template/configs/server.json +1 -0
- package/template/entities/README.md +13 -16
- package/template/entities/System/Auth/account.json +172 -16
- package/template/entities/System/Auth/account_audit.json +31 -0
- package/template/entities/System/Auth/account_device.json +64 -0
- package/template/entities/System/Auth/account_login_log.json +69 -0
- package/template/entities/System/Auth/account_oauth.json +74 -0
- package/template/entities/System/Auth/api_keys.json +18 -9
- package/template/entities/System/Auth/identity_verification.json +106 -0
- package/template/entities/System/Auth/license.json +41 -20
- package/template/entities/System/Auth/password_history.json +19 -0
- package/template/entities/System/Auth/rbac_roles.json +10 -7
- package/template/entities/System/Backup/backup_log.json +66 -0
- package/template/entities/System/Email/smtp_log.json +87 -0
- package/template/entities/System/Email/smtp_msg.json +104 -0
- package/template/entities/System/Notification/alimtalk_log.json +65 -0
- package/template/entities/System/Notification/alimtalk_msg.json +53 -0
- package/template/entities/System/Notification/friendtalk_log.json +89 -0
- package/template/entities/System/Notification/friendtalk_msg.json +91 -0
- package/template/entities/System/Notification/sms_log.json +65 -0
- package/template/entities/System/Notification/sms_msg.json +82 -0
- package/template/entities/System/Notification/sms_verification.json +50 -0
- package/template/entities/System/Payment/pg_cancel.json +60 -0
- package/template/entities/System/Payment/pg_order.json +115 -0
- package/template/entities/System/Payment/pg_webhook_log.json +52 -0
- package/template/entities/System/Push/push_log.json +86 -0
- package/template/entities/System/Push/push_msg.json +56 -0
- package/template/entities/System/Storage/file_backup_log.json +51 -0
- package/template/entities/System/Storage/file_download_log.json +43 -0
- package/template/entities/System/Storage/file_meta.json +72 -0
- package/template/entities/System/system_audit_log.json +39 -34
- package/template/entities/company.json +5 -2
- package/template/entities/{product.json → goods.json} +9 -6
- package/template/entities/todo.json +4 -2
- package/template/samples/entities/01_basic_fields.json +15 -2
- package/template/samples/entities/02_types_and_defaults.json +15 -5
- package/template/samples/entities/03_hash_and_unique.json +18 -3
- package/template/samples/entities/04_fk_and_composite_unique.json +18 -3
- package/template/samples/entities/05_cache.json +15 -9
- package/template/samples/entities/06_history_and_hard_delete.json +19 -6
- package/template/samples/entities/07_license_scope.json +18 -3
- package/template/samples/entities/08_hook_sql.json +24 -5
- package/template/samples/entities/09_hook_entity.json +12 -2
- package/template/samples/entities/10_hook_submit_delete.json +14 -5
- package/template/samples/entities/11_hook_webhook.json +20 -6
- package/template/samples/entities/12_hook_push.json +15 -2
- package/template/samples/entities/13_read_only.json +8 -4
- package/template/samples/entities/14_optimistic_lock.json +13 -2
- package/template/samples/entities/15_reset_defaults.json +7 -1
- package/template/samples/entities/16_isolated_license.json +19 -6
- package/template/scripts/reset-all.sh +57 -3
- package/template/scripts/run.sh +56 -6
- package/template/templates/email/auth/2fa_disabled.html +23 -0
- package/template/templates/email/auth/2fa_recovery_regenerated.html +31 -0
- package/template/templates/email/auth/2fa_setup_complete.html +43 -0
- package/template/templates/email/auth/welcome.html +18 -0
- package/template/templates/email/backup/backup_completed.html +35 -0
- package/template/templates/email/backup/backup_failed.html +27 -0
- package/template/templates/email/backup/backup_partial.html +31 -0
- package/template/templates/email/layout.html +47 -0
- package/template/templates/email/order/order_confirmation.html +30 -0
- package/template/templates/email/storage/storage_quota_exceeded.html +37 -0
- package/template/templates/email/storage/storage_quota_warning.html +37 -0
- package/template/templates/ocr/business_reg.json +145 -0
- package/template/templates/ocr/career_cert.json +93 -0
- package/template/templates/ocr/driver_license.json +89 -0
- package/template/templates/ocr/facility_card.json +82 -0
- package/template/templates/ocr/id_card.json +55 -0
- package/template/templates/ocr/invoice.json +92 -0
- package/template/templates/ocr/namecard.json +116 -0
- package/template/templates/ocr/prompts/business_reg.json +14 -0
- package/template/templates/ocr/prompts/career_cert.json +16 -0
- package/template/templates/ocr/prompts/driver_license.json +14 -0
- package/template/templates/ocr/prompts/facility_card.json +15 -0
- package/template/templates/ocr/prompts/general.json +13 -0
- package/template/templates/ocr/prompts/id_card.json +11 -0
- package/template/templates/ocr/prompts/invoice.json +17 -0
- package/template/templates/ocr/prompts/namecard.json +15 -0
- package/template/templates/ocr/prompts/receipt.json +14 -0
- package/template/templates/ocr/receipt.json +79 -0
- package/template/configs/cors.json +0 -7
- package/template/configs/jwt.json +0 -8
- package/template/entities/Account/account_audit.json +0 -16
- /package/template/configs/{backup.json → extensions/backup.json} +0 -0
- /package/template/configs/{storage.json → extensions/storage.json} +0 -0
- /package/template/configs/{push.json → notification/push.json} +0 -0
- /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
|
-
|
|
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
|
-
|
|
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
|
*)
|
package/template/scripts/run.sh
CHANGED
|
@@ -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
|
-
|
|
309
|
-
|
|
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
|
-
|
|
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
|
-
|
|
333
|
-
|
|
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
|
-
|
|
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
|
+
© ${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>
|