fix(core,admin_basic,board,ecommerce,page,gdpr,verification): 설정 미러·자동 정리·탈퇴 원자성·일괄 선택 범위 정비

공개이슈 ··· 및 그 전수 해소분.

- 설정 저장·복원·초기화가 상주 프로세스 미러를 갱신하도록 단일 소유자화
- 만료 데이터 자동 정리 예약 12종 신설, 파기는 기본키 배치로 분할
- 회원 탈퇴를 전 경로 원자화하고 관리자 상태변경 경로를 정식 탈퇴로 통일
- 일괄 처리 목록의 선택을 화면에 보이는 행으로 한정(selectionScope)
- 비밀번호 정책 문구를 도메인 중립 라벨로 정정
- 정적 검사 2종·검증 스킬 설계 정합성 축 신설
This commit is contained in:
HeuJung committed 2026-08-14 11:06:25 +09:00
1 parent 49c155509e
commit 4e1ec6e001
136 files changed
+7016 -504

No files matched your search

@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Api\Auth;
use App\Enums\AttachmentSourceType;
use App\Exceptions\CannotDeleteSuperAdminException;
use App\Http\Controllers\Api\Base\AuthBaseController;
use App\Http\Requests\Auth\VerifyPasswordRequest;
use App\Http\Requests\User\ChangePasswordRequest;
@@ -302,12 +303,23 @@ class ProfileController extends AuthBaseController
return $this->unauthorized('auth.unauthenticated');
}
$this->logUserActivity('profile.withdraw');
// 사용자 탈퇴 처리 (아바타, 토큰 삭제 및 익명화)
$this->userService->withdrawUser($user);
// 활동 로그는 성공한 뒤에만 남긴다 — 실패한 탈퇴가 기록으로 남으면
// 이력만 보고는 탈퇴한 것으로 읽힌다.
$this->logUserActivity('profile.withdraw');
return $this->success('user.withdraw_success');
} catch (ValidationException $e) {
// 관리자/수퍼관리자 탈퇴 차단은 요청이 잘못된 것이지 서버 오류가 아니다.
return $this->validationError($e->errors(), 'user.withdraw_failed');
} catch (CannotDeleteSuperAdminException $e) {
// 이 예외는 ValidationException 을 상속하지 않으므로 별도 매핑이 필요하다.
return $this->validationError(
['general' => [$e->getMessage()]],
'user.withdraw_failed'
);
} catch (\Exception $e) {
return $this->error('user.withdraw_failed', 500, null, ['error' => $e->getMessage()]);
}
@@ -880,25 +880,25 @@ class SaveSettingsRequest extends FormRequest
'general.site_description' => __('validation.attributes.site_description'),
'general.admin_email' => __('validation.attributes.admin_email'),
'general.timezone' => __('validation.attributes.timezone'),
'general.language' => __('validation.attributes.language'),
'general.language' => __('validation.attributes.default_language'),
// 본인인증(IDV) 필드
'identity.default_provider' => __('validation.attributes.identity_default_provider'),
'identity.purpose_providers' => __('validation.attributes.identity_purpose_providers'),
'identity.challenge_ttl_minutes' => __('validation.attributes.identity_challenge_ttl_minutes'),
'identity.max_attempts' => __('validation.attributes.identity_max_attempts'),
// notifications
'notifications.channels' => __('validation.attributes.channels'),
'notifications.channels' => __('validation.attributes.notification_channels'),
// general
'general.currency' => __('validation.attributes.currency'),
'general.currency' => __('validation.attributes.default_currency'),
'general.maintenance_mode' => __('validation.attributes.maintenance_mode'),
'general.asset_url_mode' => __('validation.attributes.asset_url_mode'),
'general.site_logo' => __('validation.attributes.site_logo'),
// mail
'mail.mailer' => __('validation.attributes.mailer'),
'mail.host' => __('validation.attributes.host'),
'mail.port' => __('validation.attributes.port'),
'mail.username' => __('validation.attributes.username'),
'mail.password' => __('validation.attributes.password'),
'mail.host' => __('validation.attributes.smtp_host'),
'mail.port' => __('validation.attributes.smtp_port'),
'mail.username' => __('validation.attributes.smtp_username'),
'mail.password' => __('validation.attributes.smtp_password'),
'mail.encryption' => __('validation.attributes.encryption'),
'mail.mailgun_domain' => __('validation.attributes.mailgun_domain'),
'mail.mailgun_secret' => __('validation.attributes.mailgun_secret'),
@@ -930,7 +930,7 @@ class SaveSettingsRequest extends FormRequest
'seo.twitter_default_card' => __('validation.attributes.twitter_default_card'),
'seo.twitter_default_site' => __('validation.attributes.twitter_default_site'),
'seo.cache_enabled' => __('validation.attributes.seo_page_cache_enabled'),
'seo.cache_ttl' => __('validation.attributes.cache_ttl'),
'seo.cache_ttl' => __('validation.attributes.seo_page_cache_ttl'),
'seo.sitemap_enabled' => __('validation.attributes.sitemap_enabled'),
'seo.sitemap_cache_ttl' => __('validation.attributes.sitemap_cache_ttl'),
'seo.sitemap_urls_per_file' => __('validation.attributes.sitemap_urls_per_file'),
+22 -3
View File
@@ -3,6 +3,7 @@
namespace App\Http\Requests\Settings;
use App\Extension\HookManager;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
/**
@@ -25,7 +26,7 @@ class TestMailRequest extends FormRequest
/**
* 검증 규칙을 반환합니다.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
@@ -72,8 +73,8 @@ class TestMailRequest extends FormRequest
'to_email.max' => __('settings.invalid_email'),
'from_address.required' => __('validation.required', ['attribute' => __('validation.attributes.from_address')]),
'from_name.required' => __('validation.required', ['attribute' => __('validation.attributes.from_name')]),
'host.required' => __('validation.required', ['attribute' => __('validation.attributes.host')]),
'port.required' => __('validation.required', ['attribute' => __('validation.attributes.port')]),
'host.required' => __('validation.required', ['attribute' => __('validation.attributes.smtp_host')]),
'port.required' => __('validation.required', ['attribute' => __('validation.attributes.smtp_port')]),
'mailgun_domain.required' => __('validation.required', ['attribute' => __('validation.attributes.mailgun_domain')]),
'mailgun_secret.required' => __('validation.required', ['attribute' => __('validation.attributes.mailgun_secret')]),
'ses_key.required' => __('validation.required', ['attribute' => __('validation.attributes.ses_key')]),
@@ -81,4 +82,22 @@ class TestMailRequest extends FormRequest
'ses_region.required' => __('validation.required', ['attribute' => __('validation.attributes.ses_region')]),
];
}
/**
* 검증 속성명을 반환합니다.
*
* 범용 필드명(host/port/username/password)은 전역 라벨이 범용 문구이므로,
* SMTP 전용 라벨을 이 요청에서만 명시 매핑합니다.
*
* @return array<string, string>
*/
public function attributes(): array
{
return [
'host' => __('validation.attributes.smtp_host'),
'port' => __('validation.attributes.smtp_port'),
'username' => __('validation.attributes.smtp_username'),
'password' => __('validation.attributes.smtp_password'),
];
}
}
@@ -6,6 +6,7 @@ use App\Enums\UserStatus;
use App\Extension\HookManager;
use App\Models\User;
use App\Rules\ExcludeCurrentUser;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
@@ -13,17 +14,20 @@ class BulkUpdateUserStatusRequest extends FormRequest
{
/**
* 요청 권한을 확인합니다.
*
* 권한 검사는 라우트의 `permission:admin,core.users.update` 미들웨어가 담당합니다.
*
* @return bool 항상 true (미들웨어에서 권한 제어)
*/
public function authorize(): bool
{
// 사용자 업데이트 권한 확인
return $this->user()->can('core.users.update');
return true;
}
/**
* 검증 규칙을 정의합니다.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
@@ -45,9 +49,9 @@ class BulkUpdateUserStatusRequest extends FormRequest
public function messages(): array
{
return [
'ids.required' => __('validation.required', ['attribute' => __('validation.attributes.ids')]),
'ids.array' => __('validation.array', ['attribute' => __('validation.attributes.ids')]),
'ids.min' => __('validation.min.array', ['attribute' => __('validation.attributes.ids'), 'min' => 1]),
'ids.required' => __('validation.required', ['attribute' => __('validation.attributes.user_ids')]),
'ids.array' => __('validation.array', ['attribute' => __('validation.attributes.user_ids')]),
'ids.min' => __('validation.min.array', ['attribute' => __('validation.attributes.user_ids'), 'min' => 1]),
'ids.*.required' => __('validation.required', ['attribute' => __('validation.attributes.user_id')]),
'ids.*.uuid' => __('validation.uuid', ['attribute' => __('validation.attributes.user_id')]),
'ids.*.exists' => __('validation.exists', ['attribute' => __('validation.attributes.user_id')]),
@@ -55,4 +59,20 @@ class BulkUpdateUserStatusRequest extends FormRequest
'status.in' => __('validation.in', ['attribute' => __('validation.attributes.status')]),
];
}
/**
* 검증 속성명을 반환합니다.
*
* 범용 필드명 ids 는 전역 라벨이 범용 문구이므로,
* 사용자 대상 라벨을 이 요청에서만 명시 매핑합니다.
*
* @return array<string, string>
*/
public function attributes(): array
{
return [
'ids' => __('validation.attributes.user_ids'),
'ids.*' => __('validation.attributes.user_id'),
];
}
}