From 2c4f62240897c045e3f79a33f9ef9d33071e5318 Mon Sep 17 00:00:00 2001
From: zenn
Date: Thu, 25 Jun 2026 17:11:31 +0900
Subject: [PATCH] =?UTF-8?q?=ED=9A=8C=EC=9B=90=20=ED=83=88=ED=87=B4?=
=?UTF-8?q?=EC=99=80=20=EB=8D=B1=20=EC=9D=BC=EA=B4=84=20=EC=82=AD=EC=A0=9C?=
=?UTF-8?q?=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
VERSION | 2 +-
admin.html | 151 ++++++++++++++++++++++++-
api/package.json | 2 +-
api/src/index.js | 4 +-
api/src/routes/admin.js | 65 +++++++++++
api/src/routes/auth.js | 28 +++++
api/src/services/accountDeletion.js | 33 ++++++
api/src/services/avatarImage.js | 8 +-
api/src/services/emailService.js | 4 +-
api/test/account-deletion.test.js | 164 ++++++++++++++++++++++++++--
docs/changelog.md | 12 ++
docs/deploy.md | 11 +-
docs/history.md | 5 +
docs/map.md | 9 +-
docs/spec.md | 16 ++-
docs/update.md | 11 ++
forgot-password.html | 6 +-
i18n/translations.en.json | 5 +
i18n/translations.ja.json | 5 +
i18n/translations.ko.json | 5 +
login.html | 8 +-
profile.html | 69 +++++++++++-
reset-password.html | 6 +-
script/api.js | 21 ++++
signup.html | 6 +-
style/output.css | 2 +-
verify-email.html | 6 +-
27 files changed, 613 insertions(+), 51 deletions(-)
create mode 100644 api/src/services/accountDeletion.js
diff --git a/VERSION b/VERSION
index 5fcb5fe..eb4ff85 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-v4.0.15
+v4.0.16
diff --git a/admin.html b/admin.html
index d199b01..a59ed39 100644
--- a/admin.html
+++ b/admin.html
@@ -3,7 +3,7 @@
-
+
관리자 - UNIARE
@@ -47,6 +47,13 @@
+
+
+
+
@@ -119,6 +126,7 @@
+
@@ -153,7 +161,22 @@
-
+
+
+
+
+
계정과 개인 정보는 삭제되며 저장한 덱은 익명으로 유지됩니다. 복구할 수 없습니다.
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
+
@@ -116,6 +116,10 @@
+
+
@@ -158,7 +162,25 @@
-
+
+
+
회원 탈퇴
+
+ 계정과 개인 정보는 삭제되며 저장한 덱은 다른 사용자가 계속 참고할 수 있도록 익명으로 유지됩니다. 이 작업은 복구할 수 없습니다.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -328,6 +350,10 @@
document.getElementById('profile-username').value = user.username || '';
document.getElementById('profile-avatar').src = resolveAvatarSrc(user);
document.getElementById('profile-avatar-remove').classList.toggle('hidden', !user.avatarUrl);
+ document.getElementById('profile-open-delete').classList.toggle(
+ 'hidden',
+ user.role === 'admin',
+ );
}
/**
@@ -462,6 +488,7 @@
});
const passwordModal = document.getElementById('password-modal');
+ const accountDeleteModal = document.getElementById('account-delete-modal');
document.getElementById('profile-open-password').addEventListener('click', () => {
document.getElementById('password-current').value = '';
@@ -505,6 +532,44 @@
if (e.target === passwordModal) passwordModal.classList.remove('open');
});
+ document.getElementById('profile-open-delete').addEventListener('click', () => {
+ const confirmationInput = document.getElementById('account-delete-confirmation');
+ confirmationInput.value = '';
+ document.getElementById('account-delete-error').classList.add('hidden');
+ document.getElementById('account-delete-yes').disabled = true;
+ accountDeleteModal.classList.add('open');
+ confirmationInput.focus();
+ });
+
+ document.getElementById('account-delete-confirmation').addEventListener('input', (event) => {
+ document.getElementById('account-delete-yes').disabled = event.target.value !== 'DELETE';
+ });
+
+ document.getElementById('account-delete-no').addEventListener('click', () => {
+ accountDeleteModal.classList.remove('open');
+ });
+
+ document.getElementById('account-delete-yes').addEventListener('click', async () => {
+ const confirmation = document.getElementById('account-delete-confirmation').value;
+ const errorEl = document.getElementById('account-delete-error');
+ const yesButton = document.getElementById('account-delete-yes');
+ errorEl.classList.add('hidden');
+ yesButton.disabled = true;
+ try {
+ await UaDeckApi.deleteMyAccount(confirmation);
+ UaDeckApi.logout();
+ location.href = '/';
+ } catch (err) {
+ errorEl.textContent = err.message || t('accountWithdrawalFailed', '회원 탈퇴에 실패했습니다.');
+ errorEl.classList.remove('hidden');
+ yesButton.disabled = confirmation !== 'DELETE';
+ }
+ });
+
+ accountDeleteModal.addEventListener('click', (event) => {
+ if (event.target === accountDeleteModal) accountDeleteModal.classList.remove('open');
+ });
+
init();
@@ -30,8 +30,8 @@
로그인으로 돌아가기
-
-
+
+
-
-
+
+
-
-
+
+
diff --git a/reset-password.html b/reset-password.html
index 057b73a..42c62ea 100644
--- a/reset-password.html
+++ b/reset-password.html
@@ -3,7 +3,7 @@