feat(service): đổi sản phẩm của dịch vụ, 2FA bắt buộc, đính kèm campaign mail, thu hồi IP

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
baodev0
2026-10-27 14:24:13 +07:00
co-authored by Claude Sonnet 5.5
parent 6315a392c0
commit a6b641015e
13 changed files with 197 additions and 9 deletions
@@ -6,6 +6,7 @@ use App\Models\Customer;
use App\Models\IpPool;
use App\Models\IpPoolLog;
use App\Services\IpPool\IpAllocationService;
use App\Services\IpPool\IpReleaseService;
use App\Support\MailAttachments;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -46,6 +47,18 @@ trait ManagesIpPoolAllocation
return response()->json(['error' => false, 'message' => "Đã thu hồi {$count} IP và gửi email thông báo"]);
}
public function release(Request $request, IpReleaseService $release): JsonResponse
{
$data = $request->validate([
'ids' => 'required|array|min:1|max:1000',
'ids.*' => 'integer',
'note' => 'nullable|string|max:2000',
]);
$count = $release->release($data['ids'], $data['note'] ?? null, $request->user('admin'));
return response()->json(['error' => false, 'message' => "Đã giải phóng {$count} IP khỏi dịch vụ/VM"]);
}
public function logs(int $id): JsonResponse
{
$pool = IpPool::withTrashed()->findOrFail($id);
@@ -13,6 +13,7 @@ use App\Models\MailCampaign;
use App\Models\Template;
use App\Services\Email\MailCampaignRenderer;
use App\Support\AutoEmailGate;
use App\Support\MailAttachments;
use App\Support\MailPlaceholders;
use App\Support\MailSettings;
use Illuminate\Http\JsonResponse;
@@ -20,6 +21,8 @@ use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Illuminate\Validation\ValidationException;
final class MailCampaignController extends Controller
@@ -106,6 +109,7 @@ final class MailCampaignController extends Controller
...$this->mapCampaign($campaign),
'cc' => $campaign->cc,
'bcc' => $campaign->bcc,
'attachments' => collect($campaign->attachments)->pluck('name')->all(),
'variables' => $campaign->variables ?? [],
'logs' => $logs,
],
@@ -204,6 +208,17 @@ final class MailCampaignController extends Controller
]);
}
public function downloadAttachment(string $uuid, int $index): StreamedResponse|JsonResponse
{
$file = MailCampaign::where('uuid', $uuid)->first()?->attachments[$index] ?? null;
if (!$file || !Storage::disk($file['disk'])->exists($file['path'])) {
return response()->json(['error' => true, 'message' => 'Không tìm thấy tệp đính kèm'], 404);
}
return Storage::disk($file['disk'])->download($file['path'], $file['name']);
}
public function resolveEmails(Request $request): JsonResponse
{
$data = $request->validate([
@@ -240,6 +255,7 @@ final class MailCampaignController extends Controller
'name' => 'nullable|string|max:255',
'cc' => 'nullable|string|max:1000',
'bcc' => 'nullable|string|max:1000',
...MailAttachments::rules(),
], [
...self::EMAIL_MESSAGES,
'uuids.*.exists' => 'Khách hàng đã chọn không còn tồn tại.',
@@ -277,6 +293,12 @@ final class MailCampaignController extends Controller
'created_by' => $admin->id ?? null,
])->save();
if (!empty($data['attachments'])) {
$campaign->update([
'attachments' => MailAttachments::store($data['attachments'], "mail-campaigns/{$campaign->uuid}"),
]);
}
foreach ($recipients as $email => $customerId) {
SendMailCampaignRecipient::dispatch($campaign->id, $customerId, $email);
}
@@ -427,13 +449,21 @@ final class MailCampaignController extends Controller
->toBase();
}
private function displaySubject(MailCampaign $campaign): string
{
$values = array_filter($campaign->variables ?? [], fn ($value) => $value !== null && $value !== '');
$subject = MailPlaceholders::fill((string) $campaign->subject, $values, escapeHtml: false);
return trim(preg_replace('/[\s|\-–,:]+$/u', '', trim($subject)) ?? $subject);
}
private function mapCampaign(MailCampaign $campaign): array
{
return [
'id' => $campaign->id,
'uuid' => $campaign->uuid,
'name' => $campaign->name,
'subject' => $campaign->subject,
'subject' => $this->displaySubject($campaign),
'template_id' => $campaign->template_id,
'template_name' => $campaign->template->name ?? null,
'total_recipients' => $campaign->total_recipients,