v7.0.0-beta.2 release
This commit is contained in:
1 parent
6595fd0eb5
commit
fa8eba7a0d
1165 files changed
+107118
-762127
No files matched your search
@@ -195,6 +195,10 @@ class UpdateLayoutContentRequest extends FormRequest
|
||||
'content.transition_overlay.spinner' => ['nullable', 'array'],
|
||||
'content.transition_overlay.spinner.component' => ['nullable', 'string', 'max:100'],
|
||||
'content.transition_overlay.spinner.text' => ['nullable', 'string', 'max:200'],
|
||||
// wait_for: spinner 가 명시된 progressive/blocking 데이터소스 fetch 완료까지 표시되도록 가드
|
||||
// background/websocket 데이터소스는 의도상 사용자 차단 불가 → withValidator 에서 cross-field 검증
|
||||
'content.transition_overlay.wait_for' => ['nullable', 'array'],
|
||||
'content.transition_overlay.wait_for.*' => ['string', 'max:100'],
|
||||
];
|
||||
|
||||
// extends 레이아웃 또는 Base 레이아웃이 아닌 경우에만 endpoint, components 필수
|
||||
@@ -225,6 +229,54 @@ class UpdateLayoutContentRequest extends FormRequest
|
||||
return HookManager::applyFilters('core.layout.update_content_validation_rules', $rules, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cross-field 검증 — transition_overlay.wait_for 가 가리키는 데이터소스의 type/loading_strategy 검증
|
||||
*
|
||||
* wait_for 는 spinner 가 fetch 완료까지 표시되어야 할 데이터소스 ID 목록이지만,
|
||||
* 의미상 사용자를 차단할 수 없는 background/websocket 데이터소스는 사전에 차단한다.
|
||||
*/
|
||||
public function withValidator(\Illuminate\Contracts\Validation\Validator $validator): void
|
||||
{
|
||||
$validator->after(function (\Illuminate\Contracts\Validation\Validator $v): void {
|
||||
$waitFor = $this->input('content.transition_overlay.wait_for');
|
||||
if (! is_array($waitFor) || empty($waitFor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$dataSources = $this->input('content.data_sources');
|
||||
if (! is_array($dataSources)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$byId = [];
|
||||
foreach ($dataSources as $source) {
|
||||
if (is_array($source) && isset($source['id'])) {
|
||||
$byId[$source['id']] = $source;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($waitFor as $index => $id) {
|
||||
if (! is_string($id) || ! isset($byId[$id])) {
|
||||
continue; // 미존재 ID 는 엔진에서 자동 무시됨 (가드 무시)
|
||||
}
|
||||
$source = $byId[$id];
|
||||
$type = $source['type'] ?? 'api';
|
||||
$strategy = $source['loading_strategy'] ?? 'progressive';
|
||||
if ($type === 'websocket') {
|
||||
$v->errors()->add(
|
||||
"content.transition_overlay.wait_for.$index",
|
||||
__('validation.layout.transition_overlay.wait_for.websocket', ['id' => $id])
|
||||
);
|
||||
} elseif ($strategy === 'background') {
|
||||
$v->errors()->add(
|
||||
"content.transition_overlay.wait_for.$index",
|
||||
__('validation.layout.transition_overlay.wait_for.background', ['id' => $id])
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 검증 오류 메시지 커스터마이징
|
||||
*
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\MailSendLog;
|
||||
|
||||
use App\Models\MailSendLog;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* 메일 발송 이력 일괄 삭제 요청을 검증합니다.
|
||||
*/
|
||||
class MailSendLogBulkDeleteRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 요청 권한을 확인합니다.
|
||||
*
|
||||
* @return bool 항상 true (권한은 permission 미들웨어에서 처리)
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 유효성 검증 규칙을 반환합니다.
|
||||
*
|
||||
* @return array<string, mixed> 검증 규칙
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'ids' => ['required', 'array', 'min:1'],
|
||||
'ids.*' => ['required', 'integer', Rule::exists(MailSendLog::class, 'id')],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 정의 유효성 검증 메시지를 반환합니다.
|
||||
*
|
||||
* @return array<string, string> 메시지
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'ids.required' => __('mail_send_log.validation.ids_required'),
|
||||
'ids.min' => __('mail_send_log.validation.ids_min'),
|
||||
'ids.*.exists' => __('mail_send_log.validation.id_not_found'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\MailSendLog;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* 메일 발송 이력 삭제 요청을 검증합니다.
|
||||
*/
|
||||
class MailSendLogDeleteRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 요청 권한을 확인합니다.
|
||||
*
|
||||
* @return bool 항상 true (권한은 permission 미들웨어에서 처리)
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 유효성 검증 규칙을 반환합니다.
|
||||
*
|
||||
* @return array<string, mixed> 검증 규칙
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\MailSendLog;
|
||||
|
||||
use App\Enums\ExtensionOwnerType;
|
||||
use App\Enums\MailSendStatus;
|
||||
use App\Extension\HookManager;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* 메일 발송 이력 목록 조회 요청을 검증합니다.
|
||||
*/
|
||||
class MailSendLogIndexRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 요청 권한을 확인합니다.
|
||||
*
|
||||
* @return bool 항상 true (권한은 permission 미들웨어에서 처리)
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 유효성 검증 규칙을 반환합니다.
|
||||
*
|
||||
* @return array<string, mixed> 검증 규칙
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'extension_type' => ['nullable', 'array'],
|
||||
'extension_type.*' => ['string', Rule::in(ExtensionOwnerType::values())],
|
||||
'extension_identifier' => ['nullable', 'array'],
|
||||
'extension_identifier.*' => ['string', 'max:100'],
|
||||
'template_type' => ['nullable', 'string', 'max:100'],
|
||||
'status' => ['nullable', 'array'],
|
||||
'status.*' => ['string', Rule::in(array_column(MailSendStatus::cases(), 'value'))],
|
||||
'search' => ['nullable', 'string', 'max:255'],
|
||||
'search_type' => ['nullable', 'string', Rule::in(['all', 'recipient_email', 'recipient_name', 'subject'])],
|
||||
'date_from' => ['nullable', 'date'],
|
||||
'date_to' => ['nullable', 'date', 'after_or_equal:date_from'],
|
||||
'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
|
||||
'sort_by' => ['nullable', 'string', Rule::in(['sent_at', 'recipient_email', 'subject', 'extension_type', 'status'])],
|
||||
'sort_order' => ['nullable', 'string', Rule::in(['asc', 'desc'])],
|
||||
];
|
||||
|
||||
return HookManager::applyFilters('core.mail_send_log.index_validation_rules', $rules, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 정의 유효성 검증 메시지를 반환합니다.
|
||||
*
|
||||
* @return array<string, string> 메시지
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'status.in' => __('mail_send_log.validation.status_invalid'),
|
||||
'date_to.after_or_equal' => __('mail_send_log.validation.date_range_invalid'),
|
||||
'per_page.min' => __('mail_send_log.validation.per_page_min'),
|
||||
'per_page.max' => __('mail_send_log.validation.per_page_max'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\MailTemplate;
|
||||
|
||||
use App\Extension\HookManager;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* 메일 템플릿 목록 조회 요청을 검증합니다.
|
||||
*/
|
||||
class MailTemplateIndexRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 요청 권한을 확인합니다.
|
||||
*
|
||||
* @return bool 항상 true (권한은 permission 미들웨어에서 처리)
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 유효성 검증 규칙을 반환합니다.
|
||||
*
|
||||
* @return array<string, mixed> 검증 규칙
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'search' => ['nullable', 'string', 'max:255'],
|
||||
'search_type' => ['nullable', 'string', Rule::in(['all', 'subject', 'body'])],
|
||||
'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
|
||||
'sort_by' => ['nullable', 'string', Rule::in(['id', 'type', 'is_active', 'created_at', 'updated_at'])],
|
||||
'sort_order' => ['nullable', 'string', Rule::in(['asc', 'desc'])],
|
||||
];
|
||||
|
||||
return HookManager::applyFilters('core.mail_template.index_validation_rules', $rules, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 정의 유효성 검증 메시지를 반환합니다.
|
||||
*
|
||||
* @return array<string, string> 메시지
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'per_page.min' => __('mail_template.validation.per_page_min'),
|
||||
'per_page.max' => __('mail_template.validation.per_page_max'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\MailTemplate;
|
||||
|
||||
use App\Extension\HookManager;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* 메일 템플릿 미리보기 요청을 검증합니다.
|
||||
*/
|
||||
class MailTemplatePreviewRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 요청 권한을 확인합니다.
|
||||
*
|
||||
* @return bool 항상 true (권한은 permission 미들웨어에서 처리)
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 유효성 검증 규칙을 반환합니다.
|
||||
*
|
||||
* @return array<string, mixed> 검증 규칙
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'subject' => ['required', 'string', 'max:500'],
|
||||
'body' => ['required', 'string', 'max:65535'],
|
||||
'variables' => ['nullable', 'array'],
|
||||
'variables.*.key' => ['required_with:variables', 'string', 'max:100'],
|
||||
];
|
||||
|
||||
return HookManager::applyFilters('core.mail_template.preview_validation_rules', $rules, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 정의 유효성 검증 메시지를 반환합니다.
|
||||
*
|
||||
* @return array<string, string> 메시지
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'subject.required' => __('mail_template.validation.subject_required'),
|
||||
'body.required' => __('mail_template.validation.body_required'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\MailTemplate;
|
||||
|
||||
use App\Extension\HookManager;
|
||||
use App\Rules\LocaleRequiredTranslatable;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* 메일 템플릿 수정 요청을 검증합니다.
|
||||
*/
|
||||
class UpdateMailTemplateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 요청 권한을 확인합니다.
|
||||
*
|
||||
* @return bool 항상 true (권한은 permission 미들웨어에서 처리)
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 유효성 검증 규칙을 반환합니다.
|
||||
*
|
||||
* @return array<string, mixed> 검증 규칙
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'subject' => ['required', 'array', new LocaleRequiredTranslatable(maxLength: 500)],
|
||||
'body' => ['required', 'array', new LocaleRequiredTranslatable(maxLength: 65535)],
|
||||
'is_active' => ['sometimes', 'boolean'],
|
||||
];
|
||||
|
||||
return HookManager::applyFilters('core.mail_template.update_validation_rules', $rules, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 정의 유효성 검증 메시지를 반환합니다.
|
||||
*
|
||||
* @return array<string, string> 메시지
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'subject.required' => __('mail_template.validation.subject_required'),
|
||||
'body.required' => __('mail_template.validation.body_required'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ class InstallModuleRequest extends FormRequest
|
||||
{
|
||||
$rules = [
|
||||
'module_name' => ['required', 'string', 'max:255', new ValidExtensionIdentifier],
|
||||
'vendor_mode' => ['nullable', 'string', 'in:auto,composer,bundled'],
|
||||
];
|
||||
|
||||
// 모듈/플러그인이 validation rules를 동적으로 추가할 수 있도록 훅 제공
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Module;
|
||||
|
||||
use App\Extension\HookManager;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* 모듈 업데이트 실행 요청 검증
|
||||
*
|
||||
* layout_strategy: 레이아웃 전략 (overwrite 또는 keep)
|
||||
* vendor_mode: Vendor 설치 모드 (auto, composer, bundled)
|
||||
*/
|
||||
class PerformModuleUpdateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 사용자가 이 요청을 수행할 권한이 있는지 확인
|
||||
*
|
||||
* 권한 체크는 라우트의 permission 미들웨어에서 수행됩니다.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 요청에 적용할 검증 규칙
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'layout_strategy' => ['nullable', 'string', 'in:overwrite,keep'],
|
||||
'vendor_mode' => ['nullable', 'string', 'in:auto,composer,bundled'],
|
||||
];
|
||||
|
||||
return HookManager::applyFilters('core.module.perform_update_rules', $rules);
|
||||
}
|
||||
|
||||
/**
|
||||
* 검증 오류 메시지 커스터마이징
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'layout_strategy.in' => __('modules.errors.invalid_layout_strategy'),
|
||||
'vendor_mode.in' => __('modules.errors.invalid_vendor_mode'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Notification;
|
||||
|
||||
use App\Extension\HookManager;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class NotificationBatchReadRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 권한 확인 (미들웨어에서 처리).
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 검증 규칙을 반환합니다.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'ids' => ['required', 'array', 'min:1', 'max:100'],
|
||||
'ids.*' => ['required', 'string'],
|
||||
];
|
||||
|
||||
return HookManager::applyFilters(
|
||||
'core.notification.filter_batch_read_rules',
|
||||
$rules
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Notification;
|
||||
|
||||
use App\Extension\HookManager;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class NotificationIndexRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 권한 확인 (미들웨어에서 처리).
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 검증 규칙을 반환합니다.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'read' => ['nullable', 'string', 'in:unread,read,all'],
|
||||
'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
|
||||
'page' => ['nullable', 'integer', 'min:1'],
|
||||
'sort_order' => ['nullable', 'string', 'in:asc,desc'],
|
||||
];
|
||||
|
||||
return HookManager::applyFilters(
|
||||
'core.notification.filter_index_rules',
|
||||
$rules
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\NotificationDefinition;
|
||||
|
||||
use App\Extension\HookManager;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class NotificationDefinitionIndexRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 권한 확인 (미들웨어에서 처리).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 검증 규칙을 반환합니다.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'search' => ['nullable', 'string', 'max:255'],
|
||||
'extension_type' => ['nullable', 'string', 'in:core,module,plugin'],
|
||||
'extension_identifier' => ['nullable', 'string', 'max:100'],
|
||||
'channel' => ['nullable', 'string', 'max:50'],
|
||||
'is_active' => ['nullable', 'boolean'],
|
||||
'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
|
||||
'sort_by' => ['nullable', 'string', 'in:id,type,extension_type,is_active,created_at,updated_at'],
|
||||
'sort_order' => ['nullable', 'string', 'in:asc,desc'],
|
||||
];
|
||||
|
||||
return HookManager::applyFilters(
|
||||
'core.notification_definition.filter_index_rules',
|
||||
$rules
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 검증 메시지를 반환합니다.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'per_page.min' => __('validation.min.numeric', ['attribute' => 'per_page', 'min' => 1]),
|
||||
'per_page.max' => __('validation.max.numeric', ['attribute' => 'per_page', 'max' => 100]),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\NotificationDefinition;
|
||||
|
||||
use App\Extension\HookManager;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateNotificationDefinitionRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 권한 확인 (미들웨어에서 처리).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 검증 규칙을 반환합니다.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'channels' => ['sometimes', 'array', 'min:1'],
|
||||
'channels.*' => ['string', 'max:50'],
|
||||
'hooks' => ['sometimes', 'array'],
|
||||
'hooks.*' => ['string', 'max:255'],
|
||||
'is_active' => ['sometimes', 'boolean'],
|
||||
];
|
||||
|
||||
return HookManager::applyFilters(
|
||||
'core.notification_definition.filter_update_rules',
|
||||
$rules,
|
||||
$this->route('definition')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\NotificationLog;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class NotificationLogBulkDeleteRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 권한 확인 (미들웨어에서 처리).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 검증 규칙을 반환합니다.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'ids' => ['required', 'array', 'min:1'],
|
||||
'ids.*' => ['integer'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 검증 메시지를 반환합니다.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'ids.required' => __('notification_log.no_items_selected'),
|
||||
'ids.min' => __('notification_log.no_items_selected'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\NotificationLog;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class NotificationLogDeleteRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 권한 확인 (미들웨어에서 처리).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 검증 규칙을 반환합니다.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\NotificationLog;
|
||||
|
||||
use App\Enums\NotificationLogStatus;
|
||||
use App\Extension\HookManager;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class NotificationLogIndexRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 권한 확인 (미들웨어에서 처리).
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 검증 규칙을 반환합니다.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'sender_user_id' => ['nullable', 'integer', Rule::exists(User::class, 'id')],
|
||||
'recipient_user_id' => ['nullable', 'integer', Rule::exists(User::class, 'id')],
|
||||
'search' => ['nullable', 'string', 'max:255'],
|
||||
'channel' => ['nullable', 'string', 'max:50'],
|
||||
'notification_type' => ['nullable', 'string', 'max:100'],
|
||||
'extension_type' => ['nullable', 'string', 'in:core,module,plugin'],
|
||||
'status' => ['nullable', 'string', Rule::enum(NotificationLogStatus::class)],
|
||||
'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
|
||||
'sort_by' => ['nullable', 'string', 'in:id,channel,notification_type,status,sent_at,created_at'],
|
||||
'sort_order' => ['nullable', 'string', 'in:asc,desc'],
|
||||
];
|
||||
|
||||
return HookManager::applyFilters(
|
||||
'core.notification_log.filter_index_rules',
|
||||
$rules
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\NotificationTemplate;
|
||||
|
||||
use App\Models\NotificationDefinition;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class PreviewNotificationTemplateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 권한 확인 (미들웨어에서 처리).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 검증 규칙을 반환합니다.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'definition_id' => ['required', 'integer', Rule::exists(NotificationDefinition::class, 'id')],
|
||||
'subject' => ['required', 'array'],
|
||||
'body' => ['required', 'array'],
|
||||
'locale' => ['sometimes', 'string', 'max:10'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 검증 메시지를 반환합니다.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'definition_id.required' => __('validation.required', ['attribute' => __('notification.definition')]),
|
||||
'definition_id.exists' => __('validation.exists', ['attribute' => __('notification.definition')]),
|
||||
'subject.required' => __('validation.required', ['attribute' => __('notification.subject')]),
|
||||
'body.required' => __('validation.required', ['attribute' => __('notification.body')]),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\NotificationTemplate;
|
||||
|
||||
use App\Extension\HookManager;
|
||||
use App\Rules\LocaleRequiredTranslatable;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateNotificationTemplateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 권한 확인 (미들웨어에서 처리).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 검증 규칙을 반환합니다.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'subject' => ['required', 'array', new LocaleRequiredTranslatable(maxLength: 500)],
|
||||
'body' => ['required', 'array', new LocaleRequiredTranslatable(maxLength: 65535)],
|
||||
'click_url' => ['nullable', 'string', 'max:500'],
|
||||
'recipients' => ['sometimes', 'nullable', 'array'],
|
||||
'recipients.*.type' => ['required', 'string', 'in:trigger_user,related_user,role,specific_users'],
|
||||
'recipients.*.value' => ['nullable'],
|
||||
'recipients.*.relation' => ['nullable', 'string', 'max:100'],
|
||||
'recipients.*.exclude_trigger_user' => ['nullable', 'boolean'],
|
||||
'is_active' => ['sometimes', 'boolean'],
|
||||
];
|
||||
|
||||
return HookManager::applyFilters(
|
||||
'core.notification_template.filter_update_rules',
|
||||
$rules,
|
||||
$this->route('template')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 검증 메시지를 반환합니다.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'subject.required' => __('validation.required', ['attribute' => __('notification.subject')]),
|
||||
'body.required' => __('validation.required', ['attribute' => __('notification.body')]),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ class InstallPluginRequest extends FormRequest
|
||||
{
|
||||
$rules = [
|
||||
'plugin_name' => ['required', 'string', 'max:255', new ValidExtensionIdentifier],
|
||||
'vendor_mode' => ['nullable', 'string', 'in:auto,composer,bundled'],
|
||||
];
|
||||
|
||||
// 모듈/플러그인이 validation rules를 동적으로 추가할 수 있도록 훅 제공
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Plugin;
|
||||
|
||||
use App\Extension\HookManager;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* 플러그인 업데이트 실행 요청 검증
|
||||
*
|
||||
* layout_strategy: 레이아웃 전략 (overwrite 또는 keep)
|
||||
* vendor_mode: Vendor 설치 모드 (auto, composer, bundled)
|
||||
*/
|
||||
class PerformPluginUpdateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 사용자가 이 요청을 수행할 권한이 있는지 확인
|
||||
*
|
||||
* 권한 체크는 라우트의 permission 미들웨어에서 수행됩니다.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 요청에 적용할 검증 규칙
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'layout_strategy' => ['nullable', 'string', 'in:overwrite,keep'],
|
||||
'vendor_mode' => ['nullable', 'string', 'in:auto,composer,bundled'],
|
||||
];
|
||||
|
||||
return HookManager::applyFilters('core.plugin.perform_update_rules', $rules);
|
||||
}
|
||||
|
||||
/**
|
||||
* 검증 오류 메시지 커스터마이징
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'layout_strategy.in' => __('plugins.errors.invalid_layout_strategy'),
|
||||
'vendor_mode.in' => __('plugins.errors.invalid_vendor_mode'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ class SearchRequest extends FormRequest
|
||||
{
|
||||
$rules = [
|
||||
// 기본 검색 파라미터
|
||||
'q' => ['nullable', 'string', 'max:200'],
|
||||
'q' => ['nullable', 'string', 'min:2', 'max:200'],
|
||||
'type' => ['nullable', 'string'],
|
||||
'sort' => ['nullable', 'string'],
|
||||
'page' => ['nullable', 'integer', 'min:1'],
|
||||
@@ -50,6 +50,7 @@ class SearchRequest extends FormRequest
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'q.min' => __('search.validation.q_min'),
|
||||
'q.max' => __('search.validation.q_max'),
|
||||
'page.integer' => __('search.validation.page_integer'),
|
||||
'page.min' => __('search.validation.page_min'),
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Requests\Settings;
|
||||
|
||||
use App\Extension\HookManager;
|
||||
use App\Models\Attachment;
|
||||
use App\Search\Engines\DatabaseFulltextEngine;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
@@ -98,6 +99,41 @@ class SaveSettingsRequest extends FormRequest
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
// GeoIP 토글 필드: 신규 필드라 초기값이 settings에 없을 수 있음.
|
||||
// Toggle 컴포넌트가 undefined → true 전환 시 비표준 타입을 보낼 수 있으므로
|
||||
// boolean으로 명시 캐스팅하여 Laravel boolean 검증 호환 보장.
|
||||
$this->castAdvancedBooleanFields([
|
||||
'advanced.geoip_enabled',
|
||||
'advanced.geoip_auto_update_enabled',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* advanced 탭의 boolean 필드를 PHP boolean으로 캐스팅합니다.
|
||||
*
|
||||
* Toggle 컴포넌트가 초기값 미존재(undefined) 상태에서 전환 시
|
||||
* 비표준 타입(문자열 "true", null 등)을 전송할 수 있으므로
|
||||
* prepareForValidation 단계에서 명시 캐스팅합니다.
|
||||
*
|
||||
* @param array<int, string> $fields dot-notation 필드 경로 배열
|
||||
*/
|
||||
private function castAdvancedBooleanFields(array $fields): void
|
||||
{
|
||||
$advanced = $this->input('advanced', []);
|
||||
$changed = false;
|
||||
|
||||
foreach ($fields as $dotPath) {
|
||||
$key = str_replace('advanced.', '', $dotPath);
|
||||
if (array_key_exists($key, $advanced)) {
|
||||
$advanced[$key] = filter_var($advanced[$key], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? false;
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($changed) {
|
||||
$this->merge(['advanced' => $advanced]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,7 +150,7 @@ class SaveSettingsRequest extends FormRequest
|
||||
|
||||
$rules = [
|
||||
// 탭 식별자
|
||||
'_tab' => ['nullable', 'string', Rule::in(['general', 'mail', 'upload', 'seo', 'security', 'drivers', 'advanced'])],
|
||||
'_tab' => ['nullable', 'string', Rule::in(['general', 'mail', 'upload', 'seo', 'security', 'drivers', 'advanced', 'notifications'])],
|
||||
|
||||
// 각 탭의 컨테이너
|
||||
'general' => ['sometimes', 'array'],
|
||||
@@ -124,13 +160,18 @@ class SaveSettingsRequest extends FormRequest
|
||||
'security' => ['sometimes', 'array'],
|
||||
'drivers' => ['sometimes', 'array'],
|
||||
'advanced' => ['sometimes', 'array'],
|
||||
'notifications' => ['sometimes', 'array'],
|
||||
'notifications.channels' => ['sometimes', 'array'],
|
||||
'notifications.channels.*.id' => ['required_with:notifications.channels', 'string', 'max:50'],
|
||||
'notifications.channels.*.is_active' => ['required_with:notifications.channels', 'boolean'],
|
||||
'notifications.channels.*.sort_order' => ['nullable', 'integer', 'min:0'],
|
||||
|
||||
// 일반 설정
|
||||
'general.site_name' => $this->getTabRules($tab, 'general', 'string|max:100'),
|
||||
'general.site_url' => $this->getTabRules($tab, 'general', 'url|max:255'),
|
||||
'general.site_description' => ['nullable', 'string', 'max:500'],
|
||||
'general.admin_email' => $this->getTabRules($tab, 'general', 'email|max:255'),
|
||||
'general.timezone' => $this->getTabRules($tab, 'general', [Rule::in(config('app.supported_timezones', ['Asia/Seoul', 'UTC']))]),
|
||||
'general.timezone' => $this->getTabRules($tab, 'general', ['timezone']),
|
||||
'general.language' => $this->getTabRules($tab, 'general', [Rule::in(self::SUPPORTED_LANGUAGES)]),
|
||||
'general.currency' => ['nullable', 'string', 'max:10'],
|
||||
'general.maintenance_mode' => ['nullable', 'boolean'],
|
||||
@@ -201,6 +242,13 @@ class SaveSettingsRequest extends FormRequest
|
||||
'advanced.core_update_github_url' => ['nullable', 'url', 'max:500'],
|
||||
'advanced.core_update_github_token' => ['nullable', 'string', 'max:500'],
|
||||
|
||||
// GeoIP 설정 (advanced 탭)
|
||||
// 마스터 토글 OFF 시 하위 필드(license_key, auto_update)는 collapse 되어 미전송됨
|
||||
// → nullable 필수 (기존 cache/debug 필드와 달리 조건부 렌더링 내부에 있음)
|
||||
'advanced.geoip_enabled' => ['nullable', 'boolean'],
|
||||
'advanced.geoip_license_key' => ['nullable', 'string', 'max:200', 'regex:/^[A-Za-z0-9_]+$/'],
|
||||
'advanced.geoip_auto_update_enabled' => ['nullable', 'boolean'],
|
||||
|
||||
// 드라이버 설정 (drivers 탭)
|
||||
'drivers.storage_driver' => $this->getTabRules($tab, 'drivers', [Rule::in(self::SUPPORTED_STORAGE_DRIVERS)]),
|
||||
'drivers.s3_bucket' => ['nullable', 'string', 'max:255'],
|
||||
@@ -219,10 +267,16 @@ class SaveSettingsRequest extends FormRequest
|
||||
'drivers.session_lifetime' => ['nullable', 'integer', 'min:1', 'max:43200'], // 1분 ~ 30일
|
||||
'drivers.queue_driver' => $this->getTabRules($tab, 'drivers', [Rule::in(self::SUPPORTED_QUEUE_DRIVERS)]),
|
||||
'drivers.websocket_enabled' => ['nullable', 'boolean'],
|
||||
'drivers.websocket_app_key' => ['nullable', 'string', 'max:255'],
|
||||
'drivers.websocket_app_id' => [Rule::requiredIf(fn () => $this->boolean('drivers.websocket_enabled')), 'nullable', 'string', 'max:255'],
|
||||
'drivers.websocket_app_key' => [Rule::requiredIf(fn () => $this->boolean('drivers.websocket_enabled')), 'nullable', 'string', 'max:255'],
|
||||
'drivers.websocket_app_secret' => [Rule::requiredIf(fn () => $this->boolean('drivers.websocket_enabled')), 'nullable', 'string', 'max:255'],
|
||||
'drivers.websocket_host' => ['nullable', 'string', 'max:255'],
|
||||
'drivers.websocket_port' => ['nullable', 'integer', 'min:1', 'max:65535'],
|
||||
'drivers.websocket_scheme' => ['nullable', Rule::in(self::SUPPORTED_WEBSOCKET_SCHEMES)],
|
||||
'drivers.websocket_verify_ssl' => ['nullable', 'boolean'],
|
||||
'drivers.websocket_server_host' => ['nullable', 'string', 'max:255'],
|
||||
'drivers.websocket_server_port' => ['nullable', 'integer', 'min:1', 'max:65535'],
|
||||
'drivers.websocket_server_scheme' => ['nullable', Rule::in(self::SUPPORTED_WEBSOCKET_SCHEMES)],
|
||||
'drivers.search_engine_driver' => ['nullable', Rule::in($this->getSupportedSearchEngineDrivers())],
|
||||
'drivers.log_driver' => $this->getTabRules($tab, 'drivers', [Rule::in(self::SUPPORTED_LOG_DRIVERS)]),
|
||||
'drivers.log_level' => $this->getTabRules($tab, 'drivers', [Rule::in(self::SUPPORTED_LOG_LEVELS)]),
|
||||
@@ -366,7 +420,7 @@ class SaveSettingsRequest extends FormRequest
|
||||
{
|
||||
$drivers = HookManager::applyFilters(
|
||||
'core.search.engine_drivers',
|
||||
['mysql-fulltext' => \App\Search\Engines\DatabaseFulltextEngine::class]
|
||||
['mysql-fulltext' => DatabaseFulltextEngine::class]
|
||||
);
|
||||
|
||||
return array_keys($drivers);
|
||||
@@ -535,12 +589,23 @@ class SaveSettingsRequest extends FormRequest
|
||||
'drivers.queue_driver.required' => __('validation.settings.queue_driver_required'),
|
||||
'drivers.queue_driver.in' => __('validation.settings.queue_driver_invalid'),
|
||||
'drivers.websocket_enabled.boolean' => __('validation.settings.websocket_enabled_boolean'),
|
||||
'drivers.websocket_app_id.required' => __('validation.settings.websocket_app_id_required'),
|
||||
'drivers.websocket_app_id.max' => __('validation.settings.websocket_app_id_max'),
|
||||
'drivers.websocket_app_key.required' => __('validation.settings.websocket_app_key_required'),
|
||||
'drivers.websocket_app_key.max' => __('validation.settings.websocket_app_key_max'),
|
||||
'drivers.websocket_app_secret.required' => __('validation.settings.websocket_app_secret_required'),
|
||||
'drivers.websocket_app_secret.max' => __('validation.settings.websocket_app_secret_max'),
|
||||
'drivers.websocket_host.max' => __('validation.settings.websocket_host_max'),
|
||||
'drivers.websocket_port.integer' => __('validation.settings.websocket_port_integer'),
|
||||
'drivers.websocket_port.min' => __('validation.settings.websocket_port_min'),
|
||||
'drivers.websocket_port.max' => __('validation.settings.websocket_port_max'),
|
||||
'drivers.websocket_scheme.in' => __('validation.settings.websocket_scheme_invalid'),
|
||||
'drivers.websocket_verify_ssl.boolean' => __('validation.settings.websocket_verify_ssl_boolean'),
|
||||
'drivers.websocket_server_host.max' => __('validation.settings.websocket_server_host_max'),
|
||||
'drivers.websocket_server_port.integer' => __('validation.settings.websocket_server_port_integer'),
|
||||
'drivers.websocket_server_port.min' => __('validation.settings.websocket_server_port_min'),
|
||||
'drivers.websocket_server_port.max' => __('validation.settings.websocket_server_port_max'),
|
||||
'drivers.websocket_server_scheme.in' => __('validation.settings.websocket_server_scheme_invalid'),
|
||||
'drivers.search_engine_driver.in' => __('validation.settings.search_engine_driver_invalid'),
|
||||
'drivers.log_driver.required' => __('validation.settings.log_driver_required'),
|
||||
'drivers.log_driver.in' => __('validation.settings.log_driver_invalid'),
|
||||
|
||||
@@ -36,7 +36,7 @@ class CreateUserRequest extends FormRequest
|
||||
'password' => ['required', 'confirmed', Password::min(8)],
|
||||
'language' => 'nullable|string|in:'.$supportedLocales,
|
||||
'country' => 'nullable|string|size:2|alpha',
|
||||
'timezone' => ['nullable', 'string', Rule::in(config('app.supported_timezones', ['Asia/Seoul']))],
|
||||
'timezone' => ['nullable', 'string', 'timezone'],
|
||||
'status' => 'nullable|string|in:active,inactive,blocked,withdrawn',
|
||||
|
||||
// 연락처 정보
|
||||
|
||||
@@ -44,7 +44,7 @@ class UpdateProfileRequest extends FormRequest
|
||||
'current_password' => ['required_with:password', 'current_password'],
|
||||
'language' => ['nullable', 'string', Rule::in($supportedLocales)],
|
||||
'country' => ['nullable', 'string', 'size:2', 'alpha'],
|
||||
'timezone' => ['nullable', 'string', Rule::in(config('app.supported_timezones', ['Asia/Seoul']))],
|
||||
'timezone' => ['nullable', 'string', 'timezone'],
|
||||
|
||||
// 연락처 정보
|
||||
'homepage' => ['nullable', 'url', 'max:255'],
|
||||
|
||||
@@ -57,7 +57,7 @@ class UpdateUserRequest extends FormRequest
|
||||
'password' => ['nullable', 'confirmed', Password::min(8)],
|
||||
'language' => 'nullable|string|in:'.$supportedLocales,
|
||||
'country' => 'nullable|string|size:2|alpha',
|
||||
'timezone' => ['nullable', 'string', Rule::in(config('app.supported_timezones', ['Asia/Seoul']))],
|
||||
'timezone' => ['nullable', 'string', 'timezone'],
|
||||
'status' => 'nullable|string|in:active,inactive,blocked,withdrawn',
|
||||
|
||||
// 연락처 정보
|
||||
|
||||
Reference in new issue
Block a user