feat: Add IT Support catalog and related migrations, seeders, and email templates

- Created migration to add 'group_label' to 'product_type_fields' table.
- Implemented ItSupportCatalogSeeder to populate IT Support features and tiers.
- Added ProductCategorySeeder for initial product categories.
- Developed ProductMockupSeeder for mockup products in various categories.
- Introduced ProductTypeFieldSeeder to define fields for different product types.
- Created email templates for VPS service information and service renewal reminders.
This commit is contained in:
baodev0
2026-08-26 16:19:38 +07:00
parent 382b7c0ef8
commit c917e3bf5e
47 changed files with 2992 additions and 1759 deletions
@@ -12,10 +12,7 @@ use App\Http\Resources\Api\Client\ClientResource;
use App\Services\ActivityLogger;
use App\Services\Api\Client\ClientAuthService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
use Tymon\JWTAuth\Facades\JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
/**
* @OA\Tag(
@@ -390,80 +387,37 @@ class ClientController extends Controller
'email' => ['required', 'email', 'max:255'],
]);
$email = strtolower(trim((string) $request->input('email')));
$customer = \App\Models\Customer::whereRaw('LOWER(email) = ?', [$email])->first();
if (! $customer) {
// Do not disclose whether the email exists.
return response()->json([
'error' => false,
'message' => 'Nếu email tồn tại, mật khẩu mới đã được gửi đến hộp thư của bạn.',
]);
}
$alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#$%^&*';
$newPassword = '';
$max = strlen($alphabet) - 1;
for ($i = 0; $i < 16; $i++) {
$newPassword .= $alphabet[random_int(0, $max)];
}
try {
\Illuminate\Support\Facades\DB::beginTransaction();
$customer->password = Hash::make($newPassword);
$customer->save();
\Illuminate\Support\Facades\Mail::to($customer->email)->later(
now()->addSeconds(5),
new \App\Mail\Customer\PasswordResetByAdmin([
'fullname' => $customer->fullname,
'email' => $customer->email,
'password' => $newPassword,
])
);
$customer = $this->authService->resetForgottenPassword($request->input('email'));
if ($customer) {
$this->activityLogger->log(
event: 'password_forgot',
logName: 'Customer',
description: "Customer {$customer->email} yêu cầu cấp lại mật khẩu",
subject: $customer,
);
\Illuminate\Support\Facades\DB::commit();
} catch (\Throwable) {
\Illuminate\Support\Facades\DB::rollBack();
return response()->json([
'error' => true,
'message' => 'Không thể gửi email, vui lòng thử lại sau.',
], 500);
}
// Do not disclose whether the email exists.
return response()->json([
'error' => false,
'message' => 'Mật khẩu mới đã được gửi đến email của bạn.',
'message' => 'Nếu email tồn tại, mật khẩu mới đã được gửi đến hộp thư của bạn.',
]);
}
public function update(ClientUpdateRequest $request)
{
$user = auth('client')->user();
$result = $this->authService->updateProfile(auth('client')->user(), $request->validated());
$user = $result['client'];
$oldValues = array_intersect_key($user->toArray(), $request->validated());
$user->fill($request->validated());
$dirty = $user->getDirty();
$user->save();
if (!empty($dirty)) {
if (!empty($result['dirty'])) {
$this->activityLogger->log(
event: 'profile_update',
logName: 'Customer',
description: "Customer {$user->email} cập nhật thông tin cá nhân: " . implode(', ', array_keys($dirty)),
description: "Customer {$user->email} cập nhật thông tin cá nhân: " . implode(', ', array_keys($result['dirty'])),
subject: $user,
oldValues: array_intersect_key($oldValues, $dirty),
newValues: array_diff_key($dirty, array_flip(['password', 'remember_token'])),
oldValues: $result['old'],
newValues: array_diff_key($result['dirty'], array_flip(['password', 'remember_token'])),
);
}
@@ -478,29 +432,25 @@ class ClientController extends Controller
public function updateCccd(ClientUpdateCccdRequest $request)
{
$user = auth('client')->user();
$frontUrl = Storage::disk('public')->url($request->file('cmnd_before')->store('cccd', 'public'));
$backUrl = Storage::disk('public')->url($request->file('cmnd_after')->store('cccd', 'public'));
$oldValues = array_intersect_key($user->toArray(), $request->validated());
$user->identity_number = $request->validated('cmnd');
$user->front_identity_number = Storage::disk('public')->url(
$request->file('cmnd_before')->store('cccd', 'public')
);
$user->back_identity_number = Storage::disk('public')->url(
$request->file('cmnd_after')->store('cccd', 'public')
$result = $this->authService->updateCccd(
auth('client')->user(),
$request->validated('cmnd'),
$frontUrl,
$backUrl,
);
$user = $result['client'];
$dirty = $user->getDirty();
$user->save();
if (!empty($dirty)) {
if (!empty($result['dirty'])) {
$this->activityLogger->log(
event: 'cccd_update',
logName: 'Customer',
description: "Customer {$user->email} cập nhật chứng minh thư",
subject: $user,
oldValues: array_intersect_key($oldValues, $dirty),
newValues: $dirty,
oldValues: $result['old'],
newValues: $result['dirty'],
);
}
@@ -517,16 +467,19 @@ class ClientController extends Controller
{
$user = auth('client')->user();
if (!Hash::check($request->input('current_password'), $user->password)) {
try {
$this->authService->changePassword(
$user,
$request->input('current_password'),
$request->input('password'),
);
} catch (\Illuminate\Validation\ValidationException $e) {
return response()->json([
'error' => true,
'message' => 'Mật khẩu hiện tại không chính xác',
'message' => collect($e->errors())->flatten()->first(),
], 422);
}
$user->password = bcrypt($request->input('password'));
$user->save();
$this->activityLogger->log(
event: 'password_change',
logName: 'Customer',