Files
portal-api/database/seeders/TemplateSeeder.php
T
baodev0andClaude Sonnet 5 12d5b4951c feat: popup quảng cáo, đặt lại mật khẩu bằng token 80 ký tự, 2FA email, vá lỗ hổng bảo mật
- Popup/banner quảng cáo cấu hình từ admin (loại, link, lịch hiển thị)
- Đặt lại mật khẩu: token 80 ký tự kèm check digit Luhn thay cho token+email lộ trong URL, chỉ lưu bản băm, một lần dùng, hết hạn 60 phút
- Xác thực 2 lớp qua email khi đăng nhập (mã 6 số, ghi nhớ thiết bị 90 ngày)
- ApiAuthCheck chặn ngay tài khoản bị khoá dù JWT còn hạn (trước đây dùng được tới hết TTL)
- Handler trả 404 thay vì 500 khi ModelNotFoundException không bọc HttpException
- Test ClientTamperingTest: xác nhận IDOR/giả mạo dữ liệu từ client đều bị chặn

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-13 13:23:33 +07:00

147 lines
4.6 KiB
PHP

<?php
namespace Database\Seeders;
use App\Models\Template;
use App\Support\EmailTemplateShell;
use App\Support\MailPlaceholders;
use Database\Seeders\EmailTemplates\AccountTemplates;
use Database\Seeders\EmailTemplates\AdminTemplates;
use Database\Seeders\EmailTemplates\BillingTemplates;
use Database\Seeders\EmailTemplates\LegacyTemplates;
use Database\Seeders\EmailTemplates\MarketingTemplates;
use Database\Seeders\EmailTemplates\ServiceTemplates;
use Database\Seeders\EmailTemplates\TemplateVariables;
use Illuminate\Database\Seeder;
class TemplateSeeder extends Seeder
{
public function run(): void
{
foreach ([...self::templates(), ...self::legacyTemplates()] as $item) {
$this->upsert($item);
}
foreach (MarketingTemplates::all() as $item) {
$this->createMarketingIfMissing($item);
}
}
public function seedScenarioIfMissing(string $scenario): void
{
if (Template::query()->where('key', $scenario)->orWhere('scenario', $scenario)->exists()) {
return;
}
foreach (self::templates() as $item) {
if ($item['scenario'] === $scenario) {
$this->upsert($item);
}
}
}
private function createMarketingIfMissing(array $item): void
{
$template = Template::firstOrNew(['name' => $item['name']]);
if ($template->exists) {
return;
}
$template->fill([
'type_id' => 0,
'format' => 'html',
'subject' => $item['subject'],
'message' => EmailTemplateShell::page($item['body']),
'status' => 1,
]);
$template->syncVariables($item['variables']);
$template->save();
}
/**
* @return array<int, array{scenario: string, name: string, subject: string, message: string, types?: array<string, string>}>
*/
public static function templates(): array
{
$layout = [
'scenario' => 'layout',
'name' => 'Bố cục Header/Footer (mặc định)',
'subject' => '',
'message' => EmailTemplateShell::page('{{$content}}'),
];
$scenarios = array_map(
fn (array $item) => [...$item, 'message' => EmailTemplateShell::page($item['body'])],
[
...AccountTemplates::all(),
...ServiceTemplates::all(),
...BillingTemplates::all(),
...AdminTemplates::all(),
],
);
return [$layout, ...$scenarios];
}
/**
* @return array<int, array{scenario: string, name: string, subject: string, message: string}>
*/
public static function legacyTemplates(): array
{
return array_map(
fn (array $item) => [...$item, 'message' => EmailTemplateShell::page($item['body'])],
LegacyTemplates::all(),
);
}
private function upsert(array $item): void
{
$scenario = $item['scenario'];
$template = Template::query()
->where(fn ($q) => $q->where('key', $scenario)->orWhere('scenario', $scenario))
->orderByRaw('`key` = ? desc', [$scenario])
->orderBy('id')
->first() ?? new Template(['type_id' => 0, 'status' => 1]);
$template->fill([
'key' => $scenario,
'scenario' => $scenario,
'name' => $item['name'],
'subject' => $item['subject'],
'message' => $item['message'],
'format' => 'html',
]);
$template->metadata = [
...($template->metadata ?? []),
'variables' => $this->variables($template, $item['types'] ?? []),
];
$template->save();
}
/**
* @param array<string, string> $typeOverrides
* @return array<int, array{key: string, label: string, type: string, example: string}>
*/
private function variables(Template $template, array $typeOverrides): array
{
$existing = $template->variables();
return collect(MailPlaceholders::extract($template->subject, $template->message))
->map(function (string $key) use ($existing, $typeOverrides) {
[$label, $type, $example] = TemplateVariables::DEFINITIONS[$key] ?? [$key, 'string', ''];
return [
'key' => $key,
'label' => $existing[$key]['label'] ?? $label,
'type' => $typeOverrides[$key] ?? $type,
'example' => $existing[$key]['example'] ?? $example,
];
})
->values()
->all();
}
}