First commit

This commit is contained in:
Saufi
2026-05-18 08:56:23 +08:00
commit fd3d3a4d2b
147 changed files with 22099 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class SplitChunkRequest extends FormRequest
{
public function authorize(): bool
{
// Hanya admin yang boleh split chunk
return auth()->check() && auth()->user()->role === 'admin';
}
public function rules(): array
{
return [
'segments' => ['required', 'array', 'min:2', 'max:10'],
'segments.*' => ['required', 'string', 'min:20', 'max:10000'],
'notes' => ['nullable', 'string', 'max:500'],
];
}
public function messages(): array
{
return [
'segments.required' => 'Sila masukkan segmen untuk split.',
'segments.min' => 'Split memerlukan sekurang-kurangnya 2 segmen.',
'segments.max' => 'Maksimum 10 segmen dibenarkan dalam satu operasi split.',
'segments.*.required' => 'Segmen tidak boleh kosong.',
'segments.*.min' => 'Setiap segmen mesti sekurang-kurangnya 20 aksara untuk embedding bermakna.',
'segments.*.max' => 'Setiap segmen tidak boleh melebihi 10,000 aksara.',
'notes.max' => 'Nota tidak boleh melebihi 500 aksara.',
];
}
protected function prepareForValidation(): void
{
// Trim setiap segmen dan buang segmen yang benar-benar kosong
if ($this->has('segments')) {
$segments = array_values(
array_filter(
array_map('trim', $this->input('segments', [])),
fn($s) => $s !== ''
)
);
$this->merge(['segments' => $segments]);
}
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class StoreCategoryRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->canManageCategories();
}
public function rules(): array
{
$categoryId = $this->route('category')?->id;
return [
'name' => ['required', 'string', 'max:100'],
'slug' => [
'nullable',
'string',
'max:100',
'regex:/^[a-z0-9-]+$/',
Rule::unique('categories', 'slug')->ignore($categoryId)->whereNull('deleted_at'),
],
'description' => ['nullable', 'string', 'max:500'],
'color' => ['nullable', 'string', 'regex:/^#[0-9A-Fa-f]{6}$/'],
'is_active' => ['boolean'],
'sort_order' => ['nullable', 'integer', 'min:0'],
];
}
public function messages(): array
{
return [
'name.required' => 'Nama kategori wajib diisi.',
'slug.unique' => 'Slug ini sudah digunakan.',
'slug.regex' => 'Slug hanya boleh mengandungi huruf kecil, angka, dan tanda (-)',
'color.regex' => 'Warna mesti dalam format hex (contoh: #3b82f6)',
];
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class StoreDocumentRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->canManageDocuments();
}
public function rules(): array
{
$maxSizeKb = config('knowledgebase.upload.max_file_size', 20480);
return [
'category_id' => ['required', 'integer', 'exists:categories,id'],
'title' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string', 'max:1000'],
'file' => [
'required',
'file',
'mimes:pdf',
"max:{$maxSizeKb}",
],
'effective_date' => ['nullable', 'date'],
'expiry_date' => ['nullable', 'date', 'after_or_equal:effective_date'],
'tags' => ['nullable', 'array'],
'tags.*' => ['string', 'max:50'],
'language' => ['nullable', 'in:ms,en'],
'change_notes' => ['nullable', 'string', 'max:500'],
];
}
public function messages(): array
{
$maxMb = round(config('knowledgebase.upload.max_file_size', 20480) / 1024);
return [
'category_id.required' => 'Kategori wajib dipilih.',
'category_id.exists' => 'Kategori tidak wujud.',
'title.required' => 'Tajuk dokumen wajib diisi.',
'file.required' => 'Fail PDF wajib diupload.',
'file.mimes' => 'Hanya fail PDF yang dibenarkan.',
'file.max' => "Saiz fail tidak boleh melebihi {$maxMb}MB.",
'expiry_date.after_or_equal' => 'Tarikh luput mesti selepas atau sama dengan tarikh kuat kuasa.',
];
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Http\Requests\Admin;
use App\Models\KnowledgeItem;
use Illuminate\Foundation\Http\FormRequest;
class StoreKnowledgeItemRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->canManageDocuments();
}
public function rules(): array
{
return [
'category_id' => ['required', 'integer', 'exists:categories,id'],
'item_type' => ['required', 'in:' . implode(',', array_keys(KnowledgeItem::typeLabels()))],
'title' => ['required', 'string', 'max:500'],
'content' => ['required', 'string', 'max:10000'],
'content_short' => ['nullable', 'string', 'max:500'],
'tags' => ['nullable', 'array'],
'tags.*' => ['string', 'max:50'],
'language' => ['nullable', 'in:ms,en'],
'effective_date' => ['nullable', 'date'],
'expiry_date' => ['nullable', 'date'],
'is_active' => ['boolean'],
'is_public' => ['boolean'],
];
}
public function messages(): array
{
return [
'category_id.required' => 'Kategori wajib dipilih.',
'item_type.required' => 'Jenis item wajib dipilih.',
'item_type.in' => 'Jenis item tidak sah.',
'title.required' => 'Tajuk/soalan wajib diisi.',
'content.required' => 'Kandungan/jawapan wajib diisi.',
'content.max' => 'Kandungan terlalu panjang (had: 10,000 karakter).',
];
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class UpdateChunkRequest extends FormRequest
{
public function authorize(): bool
{
// Hanya admin yang boleh edit chunk
return auth()->check() && auth()->user()->role === 'admin';
}
public function rules(): array
{
return [
'final_text' => ['required', 'string', 'min:20', 'max:10000'],
'notes' => ['nullable', 'string', 'max:500'],
];
}
public function messages(): array
{
return [
'final_text.required' => 'final_text tidak boleh kosong.',
'final_text.min' => 'final_text terlalu pendek. Minimum 20 aksara diperlukan untuk embedding bermakna.',
'final_text.max' => 'final_text terlalu panjang. Maksimum 10,000 aksara.',
'notes.max' => 'Nota tidak boleh melebihi 500 aksara.',
];
}
protected function prepareForValidation(): void
{
// Trim whitespace pada final_text sebelum validasi
if ($this->has('final_text')) {
$this->merge([
'final_text' => trim($this->input('final_text')),
]);
}
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Http\Requests\Chatbot;
use Illuminate\Foundation\Http\FormRequest;
class AskQuestionRequest extends FormRequest
{
public function authorize(): bool
{
return true; // Public access
}
public function rules(): array
{
return [
'question' => [
'required',
'string',
'min:3',
'max:1000',
],
'category_id' => [
'nullable',
'integer',
'exists:categories,id',
],
'session_token' => [
'nullable',
'string',
'max:64',
],
];
}
public function messages(): array
{
return [
'question.required' => 'Soalan wajib diisi.',
'question.min' => 'Soalan terlalu pendek (minimum 3 karakter).',
'question.max' => 'Soalan terlalu panjang (maksimum 1000 karakter).',
'category_id.exists' => 'Kategori tidak wujud.',
];
}
/**
* Sanitize soalan sebelum diproses.
*/
protected function prepareForValidation(): void
{
if ($this->has('question')) {
// Buang karakter kawalan berbahaya yang mungkin prompt injection
$sanitized = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $this->question);
$sanitized = trim($sanitized);
$this->merge(['question' => $sanitized]);
}
}
}