diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 41b32d9..beb8c66 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,39 +1,46 @@ name: Deploy Portal on: - push: - branches: - - main - workflow_dispatch: + push: + branches: + - production + workflow_dispatch: jobs: - deploy: - name: Deploy to Production - runs-on: ubuntu-latest + deploy: + name: Deploy to Production + runs-on: ubuntu-latest - steps: - - name: Deploy via SSH - uses: appleboy/ssh-action@v1.2.0 - with: - host: ${{ secrets.HOST }} - username: ${{ secrets.USERNAME }} - key: ${{ secrets.SSH_KEY }} - port: ${{ secrets.PORT }} - script_stop: true - script: | - set -e - cd ${{ secrets.PROJECT_PATH }} + steps: + - name: Deploy via SSH + uses: appleboy/ssh-action@v1.2.0 + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + password: ${{ secrets.SSH_PASSWORD }} + port: ${{ secrets.PORT }} + script_stop: true + script: | + set -e + DEPLOY_PATH="/var/www/vhosts/api.portal.tgs.com.vn/httpdocs" + REPO_URL="${{ secrets.REPO_URL }}" - echo "Pulling latest code..." - git pull origin main + if [ ! -d "$DEPLOY_PATH/.git" ]; then + echo "First deploy - cloning repository..." + git clone -b product "$REPO_URL" "$DEPLOY_PATH" + else + echo "Pulling latest code..." + cd "$DEPLOY_PATH" + git pull origin product + fi - echo "Installing dependencies..." - composer install --no-dev --optimize-autoloader --no-interaction --no-progress + echo "Installing dependencies..." + composer install --no-dev --optimize-autoloader --no-interaction --no-progress - echo "Running migrations..." - php artisan migrate --force + echo "Running migrations..." + php artisan migrate --force - echo "Optimizing..." - php artisan optimize + echo "Optimizing..." + php artisan optimize - echo "Deploy completed." \ No newline at end of file + echo "Deploy completed." diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index dacb821..905bef8 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -2,79 +2,141 @@ namespace App\Exceptions; +use Illuminate\Auth\AuthenticationException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Database\QueryException; -use Illuminate\Database\UniqueConstraintViolationException; +use Illuminate\Session\TokenMismatchException; +use Illuminate\Validation\ValidationException; +use Symfony\Component\HttpKernel\Exception\HttpException; use Throwable; class Handler extends ExceptionHandler { - /** - * A list of the exception types that are not reported. - * - * @var array> - */ - protected $dontReport = [ - // - ]; + protected $dontReport = []; - /** - * A list of the inputs that are never flashed for validation exceptions. - * - * @var array - */ protected $dontFlash = [ 'current_password', 'password', 'password_confirmation', ]; - /** - * Register the exception handling callbacks for the application. - * - * @return void - */ - public function register() + public function render($request, Throwable $e) { - // Duplicate constraint - $this->renderable(function (QueryException $e, $request) { + if ($request->is('api/*') || $request->is('v1/*') || $request->expectsJson()) { + return $this->renderApiException($request, $e); + } - if ($e->errorInfo[1] == 1062) { + return parent::render($request, $e); + } + + protected function unauthenticated($request, AuthenticationException $exception) + { + if ($request->is('api/*') || $request->is('v1/*') || $request->expectsJson()) { + return response()->json([ + 'error' => true, + 'status' => 401, + 'message' => 'Bạn chưa đăng nhập hoặc phiên đăng nhập đã hết hạn.', + ], 401); + } + + return redirect()->guest($exception->redirectTo() ?? route('login')); + } + + private function renderApiException($request, Throwable $e) + { + if ($e instanceof TokenMismatchException) { + return response()->json([ + 'error' => true, + 'status' => 419, + 'message' => 'Phiên làm việc đã hết hạn, vui lòng thử lại.', + ], 419); + } + + if ($e instanceof ValidationException) { + return response()->json([ + 'error' => true, + 'status' => 422, + 'message' => $e->getMessage(), + 'errors' => $e->errors(), + ], 422); + } + + if ($e instanceof AuthenticationException) { + return response()->json([ + 'error' => true, + 'status' => 401, + 'message' => 'Bạn chưa đăng nhập hoặc phiên đăng nhập đã hết hạn.', + ], 401); + } + + if ($e instanceof QueryException) { + if (($e->errorInfo[1] ?? null) == 1062) { return response()->json([ - 'error' => true, - 'message' => 'Dữ liệu đã tồn tại' + 'error' => true, + 'status' => 422, + 'message' => 'Dữ liệu đã tồn tại.', ], 422); } - }); + return response()->json([ + 'error' => true, + 'status' => 500, + 'message' => 'Lỗi cơ sở dữ liệu.', + ], 500); + } - $this->renderable(function (\Symfony\Component\HttpKernel\Exception\NotFoundHttpException $e, $request) { - if (!$request->expectsJson() && !$request->is('v1/*') && !$request->is('api/*')) { - return null; - } + if ($e instanceof HttpException) { + $status = $e->getStatusCode(); + $messages = [ + 401 => 'Bạn chưa đăng nhập.', + 402 => 'Thanh toán bắt buộc.', + 403 => 'Bạn không có quyền thực hiện hành động này.', + 404 => 'Không tìm thấy tài nguyên.', + 405 => 'Phương thức không được phép.', + 419 => 'Phiên làm việc đã hết hạn, vui lòng thử lại.', + 422 => 'Dữ liệu không hợp lệ.', + 429 => 'Quá nhiều yêu cầu, vui lòng thử lại sau.', + 500 => 'Lỗi máy chủ nội bộ.', + 502 => 'Cổng kết nối không hợp lệ.', + 503 => 'Dịch vụ tạm thời không khả dụng.', + ]; $previous = $e->getPrevious(); - if ($previous instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) { $modelClass = $previous->getModel(); - $pageType = match ($modelClass) { - \App\Models\Product::class => 'service', + $pageType = match ($modelClass) { + \App\Models\Product::class => 'service', \App\Models\Customer::class => 'client', - \App\Models\Post::class => 'post', - default => 'unknown', + \App\Models\Post::class => 'post', + default => 'unknown', }; return response()->json([ - 'error' => true, - 'message' => 'Dữ liệu không tồn tại', + 'error' => true, + 'status' => 404, + 'message' => 'Dữ liệu không tồn tại.', 'page_type' => $pageType, ], 404); } return response()->json([ - 'error' => true, - 'message' => 'API Route not found', - ], 404); - }); + 'error' => true, + 'status' => $status, + 'message' => $e->getMessage() ?: ($messages[$status] ?? 'Đã xảy ra lỗi.'), + ], $status); + } + + $status = method_exists($e, 'getStatusCode') ? $e->getStatusCode() : 500; + + return response()->json([ + 'error' => true, + 'status' => $status, + 'message' => config('app.debug') ? $e->getMessage() : 'Đã xảy ra lỗi máy chủ.', + ], $status); + } + + public function register() + { + // } } diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index ed3ef64..d593a2d 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -12,6 +12,8 @@ class VerifyCsrfToken extends Middleware * @var array */ protected $except = [ - '/administrator/ckfinder/*' + '/administrator/ckfinder/*', + 'api/*', + 'v1/*', ]; } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 6d96fec..d6a4da5 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -38,15 +38,15 @@ class RouteServiceProvider extends ServiceProvider $this->configureRateLimiting(); $this->routes(function () { - Route::middleware(['api', 'json.response']) - ->namespace($this->namespace) - ->group(base_path('routes/client.php')); - Route::prefix('api') ->middleware(['api', 'json.response']) ->namespace($this->namespace) ->group(base_path('routes/api.php')); + Route::middleware(['api', 'json.response']) + ->namespace($this->namespace) + ->group(base_path('routes/client.php')); + Route::middleware('web') ->namespace($this->namespace) ->group(base_path('routes/admin.php')); diff --git a/config/cors.php b/config/cors.php index 403ed4b..7145735 100644 --- a/config/cors.php +++ b/config/cors.php @@ -1,7 +1,6 @@ ['api/*', 'sanctum/csrf-cookie', 'v1/*'], + 'paths' => ['api/*', 'sanctum/csrf-cookie', 'login', 'logout'], 'allowed_methods' => ['*'], - 'allowed_origins' => ['*', 'http://localhost:5173/', 'http://localhost:3000/', 'http://localhost:3001/', 'https://127.0.0.1/'], + 'allowed_origins' => ['*'], 'allowed_origins_patterns' => [], 'allowed_headers' => ['*'], - 'exposed_headers' => [], - - 'max_age' => 0, - - 'supports_credentials' => false, - + 'supports_credentials' => true, ]; diff --git a/routes/client.php b/routes/client.php index 1265ca2..521cffe 100644 --- a/routes/client.php +++ b/routes/client.php @@ -175,8 +175,9 @@ Route::any('{any}', function () { return response()->json( [ 'error' => true, + 'status' => 404, 'message' => 'API Route not found', ], 404, ); -})->where('any', '.*'); +})->where('any', '^v1/.*');