• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visavi / rotor / 27389176379

12 Jun 2026 01:47AM UTC coverage: 14.159% (-0.01%) from 14.172%
27389176379

push

github

visavi
Улучшил обновление ядра

0 of 45 new or added lines in 2 files covered. (0.0%)

3 existing lines in 2 files now uncovered.

816 of 5763 relevant lines covered (14.16%)

1.64 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

2.82
/app/Http/Controllers/Admin/UpgradeController.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace App\Http\Controllers\Admin;
6

7
use App\Services\GithubService;
8
use App\Services\MigrationService;
9
use App\Services\UpgradeService;
10
use Illuminate\Http\JsonResponse;
11
use Illuminate\Http\Request;
12
use Illuminate\Support\Facades\Artisan;
13
use Illuminate\View\View;
14
use Throwable;
15

16
class UpgradeController extends AdminController
17
{
18
    public function __construct(
3✔
19
        private readonly MigrationService $migrations,
20
        private readonly UpgradeService $upgrade,
21
    ) {
22
    }
3✔
23

24
    /**
25
     * Страница обновлений
26
     */
27
    public function index(GithubService $githubService): View
×
28
    {
29
        $latestRelease = $githubService->getLatestRelease();
×
30
        $latestVersion = $githubService->getLatestVersionClean();
×
31

32
        $hasNewVersion = version_compare(ROTOR_VERSION, $latestVersion, '<');
×
33
        $pendingMigrations = $this->migrations->getPendingMigrations($this->migrationPaths());
×
34
        $newReleases = $this->upgrade->getNewReleases($githubService);
×
35
        $permErrors = $newReleases ? $this->upgrade->checkPermissions() : [];
×
36

37
        return view('admin/upgrade/index', compact(
×
38
            'hasNewVersion',
×
39
            'latestRelease',
×
40
            'pendingMigrations',
×
41
            'newReleases',
×
42
            'permErrors',
×
43
        ));
×
44
    }
45

46
    /**
47
     * Скачивает и распаковывает архив релиза
48
     */
NEW
49
    public function download(Request $request, GithubService $githubService): JsonResponse
×
50
    {
51
        $tag = (string) $request->input('tag');
×
NEW
52
        $asset = $tag ? $this->upgrade->findAsset($githubService, $tag) : null;
×
53

NEW
54
        if (! $asset) {
×
55
            return response()->json(['error' => __('admin.upgrade.invalid_params')], 422);
×
56
        }
57

58
        ini_set('max_execution_time', 0);
×
59
        set_time_limit(0);
×
60

61
        try {
NEW
62
            $this->upgrade->downloadRelease($tag, $asset['browser_download_url']);
×
63
        } catch (Throwable $e) {
×
64
            return response()->json(['error' => $e->getMessage()], 500);
×
65
        }
66

67
        return response()->json(['success' => true]);
×
68
    }
69

70
    /**
71
     * Применяет скачанное обновление
72
     */
73
    public function apply(Request $request): JsonResponse
×
74
    {
75
        $tag = (string) $request->input('tag');
×
76

77
        if (! $tag) {
×
78
            return response()->json(['error' => __('admin.upgrade.invalid_params')], 422);
×
79
        }
80

81
        // Копирование тысяч файлов на shared-хостинге легко превышает max_execution_time,
82
        // а фатальный таймаут не выполнит finally и оставит сайт в maintenance mode
NEW
83
        ini_set('max_execution_time', 0);
×
NEW
84
        set_time_limit(0);
×
NEW
85
        ignore_user_abort(true);
×
86

UNCOV
87
        Artisan::call('down');
×
88

89
        try {
90
            $errors = $this->upgrade->applyUpdate($tag);
×
91
            $this->upgrade->cleanup($tag);
×
92
        } catch (Throwable $e) {
×
93
            return response()->json(['error' => $e->getMessage()], 500);
×
94
        } finally {
95
            Artisan::call('up');
×
96
        }
97

98
        Artisan::call('cache:clear');
×
99
        Artisan::call('route:clear');
×
100
        Artisan::call('config:clear');
×
101

NEW
102
        if (function_exists('opcache_reset')) {
×
NEW
103
            opcache_reset();
×
104
        }
105

106
        return response()->json([
×
107
            'success' => true,
×
108
            'errors'  => $errors,
×
109
        ]);
×
110
    }
111

112
    /**
113
     * Выполняет одну следующую миграцию
114
     */
115
    public function migrateNext(): JsonResponse
×
116
    {
117
        ini_set('max_execution_time', 0);
×
118
        set_time_limit(0);
×
119

120
        $pending = $this->migrations->getPendingMigrations($this->migrationPaths());
×
121

122
        if (empty($pending)) {
×
123
            Artisan::call('cache:clear');
×
124
            Artisan::call('route:clear');
×
125
            Artisan::call('config:clear');
×
126

127
            return response()->json(['done' => true, 'migration' => null, 'output' => '']);
×
128
        }
129

130
        $name = $pending[0];
×
131
        $file = $this->migrations->findFile($name);
×
132

133
        if (! $file) {
×
134
            return response()->json(['error' => "Файл миграции не найден: {$name}"], 500);
×
135
        }
136

137
        $remaining = count($pending) - 1;
×
138

139
        return response()->json([
×
140
            'done'      => $remaining === 0,
×
141
            'migration' => $name,
×
142
            'output'    => $this->migrations->runOne($file),
×
143
            'remaining' => $remaining,
×
144
        ]);
×
145
    }
146

147
    /**
148
     * Пути к папкам с миграциями
149
     */
150
    private function migrationPaths(): array
×
151
    {
152
        return [database_path('migrations'), database_path('upgrades')];
×
153
    }
154
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc