From afda333c2580259415e5e6acd8f62eaa0784db1a Mon Sep 17 00:00:00 2001 From: F4ilji Date: Sun, 18 Jan 2026 01:20:16 +0500 Subject: [PATCH] Updated nginx prod configuration Added schedule upload functionality in Filament Updated .gitignore in public directory --- _docker/nginx/prod/conf.d/nginx.conf | 47 ++++++--- app/Filament/Resources/ScheduleResource.php | 2 +- .../Pages/UploadSchedules.php | 99 ++++++++++++++----- public/.gitignore | 2 + 4 files changed, 115 insertions(+), 35 deletions(-) diff --git a/_docker/nginx/prod/conf.d/nginx.conf b/_docker/nginx/prod/conf.d/nginx.conf index dd40912..5f12227 100644 --- a/_docker/nginx/prod/conf.d/nginx.conf +++ b/_docker/nginx/prod/conf.d/nginx.conf @@ -14,12 +14,10 @@ server { return 301 https://www.ntspi.ru$request_uri; } - server { listen 443 ssl http2; server_name www.ntspi.ru; - # SSL-конфигурация ssl_certificate /etc/letsencrypt/live/www.ntspi.ru/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/www.ntspi.ru/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/www.ntspi.ru/chain.pem; @@ -34,7 +32,6 @@ server { resolver 8.8.8.8 8.8.4.4 valid=300s; resolver_timeout 5s; - # Ваши текущие настройки gzip on; gzip_disable "msie6"; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml; @@ -49,13 +46,46 @@ server { root /var/www/public; - # Security headers add_header X-Frame-Options "SAMEORIGIN"; add_header X-Content-Type-Options "nosniff"; add_header X-XSS-Protection "1; mode=block"; add_header Referrer-Policy "strict-origin-when-cross-origin"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; + location ~ ^/(upload|storage|images|docs|files)/.*\.php$ { + deny all; + return 404; + } + + location ~ ^/storage/app/public/upload/iblock/.*\.php$ { + deny all; + return 404; + } + + location ~ ^/vendor/.*\.php$ { + deny all; + return 404; + } + + location ~ /vikon_core/internal/config\.php$ { + deny all; + return 404; + } + + location ~ ^/vikon_core/internal/ { + deny all; + return 404; + } + + location ~ ^/vikon_core/(?!update/index\.php|update/make_executor\.php).*\.php$ { + deny all; + return 404; + } + + location ~ /\.(?!well-known).* { + deny all; + } + location / { add_header Access-Control-Allow-Origin *; try_files $uri /index.php?$args; @@ -99,16 +129,9 @@ server { fastcgi_param HTTPS On; } - location ~ /\.ht { - deny all; - } - - - - # Блок для обновления Let's Encrypt (оставить!) location ^~ /.well-known/acme-challenge/ { root /var/www/certbot; allow all; default_type "text/plain"; } -} +} \ No newline at end of file diff --git a/app/Filament/Resources/ScheduleResource.php b/app/Filament/Resources/ScheduleResource.php index c9aa90d..b6989e8 100644 --- a/app/Filament/Resources/ScheduleResource.php +++ b/app/Filament/Resources/ScheduleResource.php @@ -172,7 +172,7 @@ class ScheduleResource extends Resource 'index' => Pages\ListSchedules::route('/'), 'create' => Pages\CreateSchedule::route('/create'), 'edit' => Pages\EditSchedule::route('/{record}/edit'), - 'upload' => Pages\ScheduleResource\Pages\UploadSchedules::route('/upload'), // Добавьте эту строку + 'upload' => Pages\ScheduleResource\Pages\UploadSchedules::route('/upload'), ]; } } \ No newline at end of file diff --git a/app/Filament/Resources/ScheduleResource/Pages/ScheduleResource/Pages/UploadSchedules.php b/app/Filament/Resources/ScheduleResource/Pages/ScheduleResource/Pages/UploadSchedules.php index de96842..38c7ff7 100644 --- a/app/Filament/Resources/ScheduleResource/Pages/ScheduleResource/Pages/UploadSchedules.php +++ b/app/Filament/Resources/ScheduleResource/Pages/ScheduleResource/Pages/UploadSchedules.php @@ -104,11 +104,7 @@ class UploadSchedules extends Page return true; // Уже обработан, ничего не делаем } - $groupTitleParts = explode(' ', $originalFileName, 3); - $groupSearchTitle = implode(' ', array_slice($groupTitleParts, 0, 2)); - - $educationalGroup = EducationalGroup::where('title', 'like', $groupSearchTitle . '%') - ->first(); + $educationalGroup = $this->findEducationalGroupByFileName($originalFileName); if (!$educationalGroup) { $this->failedFiles[] = $originalFileNameWithExtension; @@ -157,11 +153,6 @@ class UploadSchedules extends Page }); $this->processedFiles[] = $originalFileNameWithExtension; - Notification::make() - ->title('Расписание успешно загружено') - ->body("Файл `{$originalFileNameWithExtension}` для группы `{$educationalGroup->title}`") - ->success() - ->send(); return true; } catch (\Throwable $e) { @@ -199,20 +190,30 @@ class UploadSchedules extends Page } // Отправляем итоговое уведомление - if (!empty($this->processedFiles) || !empty($this->failedFiles)) { - $message = ''; - if (!empty($this->processedFiles)) { - $message .= 'Успешно обработаны: ' . implode(', ', $this->processedFiles) . '. '; - } - if (!empty($this->failedFiles)) { - $message .= 'Не удалось обработать: ' . implode(', ', $this->failedFiles) . '. '; + $hasProcessed = !empty($this->processedFiles); + $hasFailed = !empty($this->failedFiles); + + if ($hasProcessed || $hasFailed) { + $notification = Notification::make(); + + if ($hasProcessed && $hasFailed) { + $notification + ->title('Частично обработано') + ->body('Успешно: ' . count($this->processedFiles) . '. Ошибки: ' . count($this->failedFiles) . '. Подробности в логах.') + ->warning(); + } elseif ($hasProcessed) { + $notification + ->title('Все файлы успешно загружены') + ->body('Успешно обработано: ' . count($this->processedFiles) . ' файл(ов)') + ->success(); + } else { // $hasFailed only + $notification + ->title('Ошибка при обработке файлов') + ->body('Ошибки при обработке: ' . count($this->failedFiles) . ' файл(ов)') + ->danger(); } - Notification::make() - ->title('Результаты загрузки файлов') - ->body($message) - ->info() - ->send(); + $notification->send(); } // Очищаем форму и связанные свойства после обработки @@ -242,4 +243,58 @@ class UploadSchedules extends Page { return __('Расписание и группы'); } + + /** + * Находит образовательную группу по названию файла, используя различные стратегии сопоставления + * + * @param string $fileName + * @return EducationalGroup|null + */ + protected function findEducationalGroupByFileName(string $fileName): ?EducationalGroup + { + // Удаляем расширение файла, если оно есть + $fileNameWithoutExt = pathinfo($fileName, PATHINFO_FILENAME); + + // Попытка 1: поиск точного совпадения + $exactMatch = EducationalGroup::where('title', $fileNameWithoutExt)->first(); + if ($exactMatch) { + return $exactMatch; + } + + // Попытка 2: используем регулярные выражения для поиска групп вида БФКф-2531, Нт-102 и т.д. + // Шаблон: любая последовательность букв/цифр с дефисом и цифрой + if (preg_match('/([А-Яа-яЁёA-Za-z]+[-_\s]?[\dА-Яа-яЁёA-Za-z]*\d)/u', $fileName, $matches)) { + $potentialGroupCode = trim($matches[1]); + + // Убираем лишние символы вроде пробелов и дефисов в конце + $potentialGroupCode = preg_replace('/[-_\s]+$/', '', $potentialGroupCode); + + // Попытка найти группу по коду + $group = EducationalGroup::where('title', 'like', '%' . $potentialGroupCode . '%')->first(); + if ($group) { + return $group; + } + } + + // Попытка 3: разбиение на слова и поиск по первым двум словам (старая логика) + $words = preg_split('/\s+/', $fileName, 3); // разбиваем на 3 части максимум + if (count($words) >= 2) { + $groupTitleFromWords = $words[0] . ' ' . $words[1]; + $group = EducationalGroup::where('title', 'like', $groupTitleFromWords . '%')->first(); + if ($group) { + return $group; + } + } + + // Попытка 4: если осталось только одно слово + if (isset($words[0])) { + $group = EducationalGroup::where('title', 'like', $words[0] . '%')->first(); + if ($group) { + return $group; + } + } + + // Если ничего не нашли, возвращаем null + return null; + } } diff --git a/public/.gitignore b/public/.gitignore index 49e973d..ea43bbd 100644 --- a/public/.gitignore +++ b/public/.gitignore @@ -12,3 +12,5 @@ robots.txt ./sveden ./abitur sitemap.xml +yandex_ab757c77d75e2b8d.html +vikon_core \ No newline at end of file