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

SameOldNick / SameOldWebsite / 21471360437

29 Jan 2026 08:41AM UTC coverage: 87.487% (-0.02%) from 87.511%
21471360437

push

github

web-flow
Continues to next action if tests fail

28499 of 32575 relevant lines covered (87.49%)

696.55 hits per line

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

93.87
/app/Http/Controllers/Api/Backup/BackupDestinationsController.php
1
<?php
2

3
namespace App\Http\Controllers\Api\Backup;
4

5
use App\Components\Websockets\Notifiers\JobStatusNotifier;
6
use App\Http\Controllers\Controller;
7
use App\Jobs\FilesystemConfigurationTestJob;
8
use App\Models\FilesystemConfiguration;
9
use App\Models\FilesystemConfigurationFTP;
10
use App\Models\FilesystemConfigurationSFTP;
11
use App\Rules\Slugified;
12
use Exception;
13
use Illuminate\Http\Request;
14
use Illuminate\Support\Arr;
15
use Illuminate\Support\Facades\DB;
16
use Illuminate\Validation\Rule;
17
use Spatie\Backup\Config\Config;
18

19
class BackupDestinationsController extends Controller
20
{
21
    public function __construct()
22
    {
23
        $this->middleware('can:role-manage-backups');
66✔
24
    }
25

26
    /**
27
     * List all configurations
28
     *
29
     * @return void
30
     */
31
    public function index()
32
    {
33
        return response()->json(FilesystemConfiguration::all());
3✔
34
    }
35

36
    /**
37
     * Store a new configuration
38
     *
39
     * @return mixed
40
     */
41
    public function store(Request $request)
42
    {
43
        $request->validate([
18✔
44
            'enable' => 'required|boolean',
18✔
45
            'name' => [
18✔
46
                'required',
18✔
47
                'string',
18✔
48
                'max:255',
18✔
49
                new Slugified,
18✔
50
                Rule::unique(FilesystemConfiguration::class),
18✔
51
            ],
18✔
52
            'type' => 'required|in:ftp,sftp',
18✔
53
            'host' => 'required|string|max:255',
18✔
54
            'port' => 'required|integer|min:1|max:65535',
18✔
55
            'username' => 'required|string|max:255',
18✔
56
            'auth_type' => 'nullable|required_if:type,sftp|string|in:password,key',
18✔
57
            'password' => [
18✔
58
                'nullable',
18✔
59
                Rule::requiredIf(fn () => $request->type === 'ftp' || ($request->type === 'sftp' && $request->auth_type === 'password')),
18✔
60
                'string',
18✔
61
                'max:255',
18✔
62
            ],
18✔
63
            'root' => 'nullable|string|max:255',
18✔
64
            'private_key' => [
18✔
65
                'nullable',
18✔
66
                Rule::requiredIf(fn () => $request->type === 'sftp' && $request->auth_type === 'key'),
18✔
67
                'string',
18✔
68
            ],
18✔
69
            'passphrase' => 'nullable|string|max:255',
18✔
70
            'extra' => 'nullable|array',
18✔
71
        ]);
18✔
72

73
        $config = match ($request->type) {
18✔
74
            'ftp' => FilesystemConfigurationFTP::create($request->only([
6✔
75
                'host',
6✔
76
                'port',
6✔
77
                'username',
6✔
78
                'password',
6✔
79
                'root',
6✔
80
                'extra',
6✔
81
            ])),
6✔
82
            'sftp' => FilesystemConfigurationSFTP::create($request->only([
12✔
83
                'host',
12✔
84
                'port',
12✔
85
                'username',
12✔
86
                'password',
12✔
87
                'private_key',
12✔
88
                'passphrase',
12✔
89
                'root',
12✔
90
                'extra',
12✔
91
            ])),
12✔
92
            default => null
×
93
        };
18✔
94

95
        // The validator shouldn't allow this, but just in case.
96
        if (! $config) {
18✔
97
            return response()->json(['message' => 'Type is invalid.'], 500);
×
98
        }
99

100
        $fsConfig = new FilesystemConfiguration([
18✔
101
            'name' => $request->name,
18✔
102
            'disk_type' => $request->type,
18✔
103
            'is_active' => $request->boolean('enable'),
18✔
104
        ]);
18✔
105

106
        $fsConfig->configurable()->associate($config);
18✔
107

108
        $fsConfig->save();
18✔
109

110
        return response()->json([
18✔
111
            'message' => 'Backup destination created successfully.',
18✔
112
            'configuration' => $fsConfig,
18✔
113
        ], 201);
18✔
114
    }
115

116
    /**
117
     * Show a specific configuration
118
     *
119
     * @return mixed
120
     */
121
    public function show(Config $backupConfig, FilesystemConfiguration $destination)
122
    {
123
        $config = $destination->configurable;
3✔
124

125
        if (! $config) {
3✔
126
            return response()->json(['message' => 'Configuration not found.'], 404);
×
127
        }
128

129
        // Pull disks indirectly through Spatie Backup config
130
        $enabled = $backupConfig->backup->destination->disks;
3✔
131

132
        return [
3✔
133
            'enable' => in_array($config->driver_name, $enabled),
3✔
134
            ...$destination->toArray(),
3✔
135
        ];
3✔
136
    }
137

138
    /**
139
     * Test the destination works.
140
     *
141
     * @return mixed
142
     */
143
    public function test(Request $request, FilesystemConfiguration $destination)
144
    {
145
        $notifier = JobStatusNotifier::create($request->user())->openChannel();
×
146

147
        $this->dispatch(new FilesystemConfigurationTestJob($notifier, $destination));
×
148

149
        return ['uuid' => (string) $notifier->getUuid()];
×
150
    }
151

152
    /**
153
     * Update an existing configuration
154
     *
155
     * @return mixed
156
     */
157
    public function update(Request $request, FilesystemConfiguration $destination)
158
    {
159
        $request->validate([
33✔
160
            'enable' => 'nullable|boolean',
33✔
161
            'name' => [
33✔
162
                'nullable',
33✔
163
                'string',
33✔
164
                'max:255',
33✔
165
                new Slugified,
33✔
166
                Rule::unique(FilesystemConfiguration::class)->ignore($destination),
33✔
167
            ],
33✔
168
            'host' => 'nullable|string|max:255',
33✔
169
            'port' => 'nullable|integer|min:1|max:65535',
33✔
170
            'username' => 'nullable|string|max:255',
33✔
171
            'auth_type' => 'nullable|string|in:password,key',
33✔
172
            'password' => [
33✔
173
                'nullable',
33✔
174
                'required_if:auth_type,password',
33✔
175
                'string',
33✔
176
                'max:255',
33✔
177
            ],
33✔
178
            'private_key' => [
33✔
179
                'nullable',
33✔
180
                'required_if:auth_type,key',
33✔
181
                'string',
33✔
182
            ],
33✔
183
            'passphrase' => 'nullable|string|max:255',
33✔
184
            'root' => 'nullable|string|max:255',
33✔
185

186
            'extra' => 'nullable|array',
33✔
187
        ]);
33✔
188

189
        $config = $destination->configurable;
27✔
190

191
        if (! $config) {
27✔
192
            return response()->json(['message' => 'Backup destination not found.'], 404);
×
193
        }
194

195
        $input = $request->except(['enable']);
27✔
196

197
        // Only include enable if it exists
198
        if ($request->has('enable')) {
27✔
199
            // Convert it to a boolean
200
            $input['enable'] = $request->boolean('enable');
6✔
201
        }
202

203
        $this->performUpdate($destination, $input);
27✔
204

205
        return [
27✔
206
            'message' => 'Backup destination updated successfully.',
27✔
207
            'configuration' => $destination,
27✔
208
        ];
27✔
209
    }
210

211
    /**
212
     * Updates multiple configurations
213
     *
214
     * @return mixed
215
     */
216
    public function bulkUpdate(Request $request)
217
    {
218
        $validated = $request->validate([
3✔
219
            'destinations' => 'required|array',
3✔
220
            'destinations.*.id' => [
3✔
221
                'required',
3✔
222
                'numeric',
3✔
223
                Rule::exists(FilesystemConfiguration::class),
3✔
224
            ],
3✔
225
            'destinations.*.enable' => 'nullable|boolean',
3✔
226
            'destinations.*.name' => Rule::forEach(fn (?string $value, string $attribute) => [
3✔
227
                'nullable',
3✔
228
                'string',
3✔
229
                'max:255',
3✔
230
                new Slugified,
3✔
231
                Rule::unique(FilesystemConfiguration::class)->ignore($value, 'name'),
3✔
232
            ]),
3✔
233
            'destinations.*.host' => 'nullable|string|max:255',
3✔
234
            'destinations.*.port' => 'nullable|integer|min:1|max:65535',
3✔
235
            'destinations.*.username' => 'nullable|string|max:255',
3✔
236
            'destinations.*.auth_type' => 'nullable|string|in:password,key',
3✔
237
            'destinations.*.password' => [
3✔
238
                'nullable',
3✔
239
                'required_if:auth_type,password',
3✔
240
                'string',
3✔
241
                'max:255',
3✔
242
            ],
3✔
243
            'destinations.*.private_key' => [
3✔
244
                'nullable',
3✔
245
                'required_if:auth_type,key',
3✔
246
                'string',
3✔
247
            ],
3✔
248
            'destinations.*.passphrase' => 'nullable|string|max:255',
3✔
249
            'destinations.*.root' => 'nullable|string|max:255',
3✔
250

251
            'destinations.*.extra' => 'nullable|array',
3✔
252
        ]);
3✔
253

254
        // Use a transaction to ensure atomicity
255
        DB::beginTransaction();
3✔
256

257
        try {
258
            foreach ($validated['destinations'] as $data) {
3✔
259
                $destination = FilesystemConfiguration::find($data['id']);
3✔
260

261
                $this->performUpdate($destination, Arr::except($data, 'id'));
3✔
262
            }
263

264
            // Commit the transaction if all updates succeed
265
            DB::commit();
3✔
266
        } catch (Exception $e) {
×
267
            // Rollback the transaction if anything fails
268
            DB::rollBack();
×
269

270
            return response()->json(['error' => 'Failed to update destinations.'], 500);
×
271
        }
272

273
        return [
3✔
274
            'message' => 'Backup destinations updated successfully.',
3✔
275
        ];
3✔
276
    }
277

278
    /**
279
     * Delete a configuration
280
     *
281
     * @return mixed
282
     */
283
    public function destroy(FilesystemConfiguration $destination)
284
    {
285
        $this->performDestroy($destination);
3✔
286

287
        return ['message' => 'Backup destination deleted successfully.'];
3✔
288
    }
289

290
    /**
291
     * Delete multiple configurations
292
     *
293
     * @return mixed
294
     */
295
    public function bulkDestroy(Request $request)
296
    {
297
        $validated = $request->validate([
3✔
298
            'destinations' => 'required|array',
3✔
299
            'destinations.*' => [
3✔
300
                'required',
3✔
301
                'numeric',
3✔
302
                Rule::exists(FilesystemConfiguration::class, 'id'),
3✔
303
            ],
3✔
304
        ]);
3✔
305

306
        // Use a transaction to ensure atomicity
307
        DB::beginTransaction();
3✔
308

309
        try {
310
            foreach ($validated['destinations'] as $id) {
3✔
311
                $destination = FilesystemConfiguration::find($id);
3✔
312

313
                $this->performDestroy($destination);
3✔
314
            }
315

316
            // Commit the transaction if all updates succeed
317
            DB::commit();
3✔
318
        } catch (Exception $e) {
×
319
            // Rollback the transaction if anything fails
320
            DB::rollBack();
×
321

322
            return response()->json(['error' => 'Failed to delete destinations.'], 500);
×
323
        }
324

325
        return ['message' => 'Backup destinations deleted successfully.'];
3✔
326
    }
327

328
    /**
329
     * Updates a configuration
330
     *
331
     * @return FilesystemConfiguration
332
     */
333
    protected function performUpdate(FilesystemConfiguration $destination, array $input)
334
    {
335
        $data = Arr::only($input, [
30✔
336
            'enable',
30✔
337
            'host',
30✔
338
            'port',
30✔
339
            'username',
30✔
340
            'passphrase',
30✔
341
            'extra',
30✔
342
        ]);
30✔
343

344
        if (Arr::has($input, 'enable')) {
30✔
345
            $destination->is_active = (bool) Arr::get($input, 'enable');
6✔
346
        }
347

348
        if (Arr::has($input, 'name')) {
30✔
349
            $destination->name = Arr::get($input, 'name');
6✔
350
        }
351

352
        if ($destination->isDirty()) {
30✔
353
            $destination->save();
12✔
354
        }
355

356
        $diskType = $destination->disk_type;
30✔
357
        $authType = Arr::get($input, 'auth_type');
30✔
358

359
        if (
360
            Arr::has($input, 'password') && (
30✔
361
                $authType &&
30✔
362
                ($diskType === 'ftp' || ($diskType === 'sftp' && $authType === 'password'))
30✔
363
            )
364
        ) {
365
            $data['private_key'] = null;
3✔
366
            $data['passphrase'] = null;
3✔
367
            $data['password'] = Arr::get($input, 'password');
3✔
368
        } elseif (
369
            Arr::has($input, 'private_key') && (
27✔
370
                $authType && $diskType === 'sftp' && $authType === 'key'
27✔
371
            )
372
        ) {
373
            $data['password'] = null;
3✔
374
            $data['private_key'] = Arr::get($input, 'private_key');
3✔
375
            $data['passphrase'] = Arr::get($input, 'passphrase');
3✔
376
        }
377

378
        if ($diskType === 'ftp' && Arr::has($input, 'root')) {
30✔
379
            $data['root'] = Arr::get($input, 'root');
6✔
380
        }
381

382
        $destination->configurable->update($data);
30✔
383

384
        return $destination;
30✔
385
    }
386

387
    /**
388
     * Removes a configuration
389
     *
390
     * @return void
391
     */
392
    protected function performDestroy(FilesystemConfiguration $destination)
393
    {
394
        $destination->configurable->delete();
6✔
395
        $destination->delete();
6✔
396
    }
397
}
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