SonarDeskRelease::latest()->paginate(30), 'publishedCount' => SonarDeskRelease::where('is_published', true)->count(), 'downloadCount' => SonarDeskRelease::sum('download_count'), ]); } public function create() { return view('admin.releases.form', ['release' => new SonarDeskRelease]); } public function edit(SonarDeskRelease $release) { return view('admin.releases.form', compact('release')); } public function store(Request $request): RedirectResponse { $release = new SonarDeskRelease; $this->persist($request, $release); return redirect()->route('admin.releases.index')->with('status', 'Release created.'); } public function update(Request $request, SonarDeskRelease $release): RedirectResponse { $this->persist($request, $release); return redirect()->route('admin.releases.index')->with('status', 'Release updated.'); } public function destroy(SonarDeskRelease $release): RedirectResponse { if ($release->file_path) Storage::disk('local')->delete($release->file_path); $release->delete(); return back()->with('status', 'Release deleted.'); } public function settings() { $keys = ['downloads.eyebrow','downloads.title','downloads.intro','downloads.requirements','downloads.support_text']; $settings = collect($keys)->mapWithKeys(fn ($key) => [$key => SiteSetting::valueFor($key)]); return view('admin.releases.settings', compact('settings')); } public function updateSettings(Request $request): RedirectResponse { $data = $request->validate([ 'eyebrow' => ['required','string','max:80'], 'title' => ['required','string','max:120'], 'intro' => ['required','string','max:500'], 'requirements' => ['required','string','max:500'], 'support_text' => ['required','string','max:500'], ]); foreach ($data as $key => $value) SiteSetting::put('downloads.'.$key, $value); return back()->with('status', 'Download page content updated.'); } private function persist(Request $request, SonarDeskRelease $release): void { $data = $request->validate([ 'version' => ['required','string','max:40'], 'build_number' => ['required','integer','min:1'], 'channel' => ['required', Rule::in(['stable','beta'])], 'minimum_macos' => ['nullable','string','max:30'], 'release_notes' => ['nullable','string','max:20000'], 'sparkle_signature' => ['nullable','string','max:2000'], 'package' => [ $release->exists ? 'nullable' : 'required', 'file', 'max:1048576', function (string $attribute, $value, \Closure $fail): void { $extension = strtolower($value->getClientOriginalExtension()); if (! in_array($extension, ['dmg', 'zip'], true)) { $fail('The package must be a DMG or ZIP file.'); } }, ], 'is_published' => ['nullable','boolean'], 'is_featured' => ['nullable','boolean'], ]); $data['is_published'] = $request->boolean('is_published'); $data['is_featured'] = $request->boolean('is_featured'); if ($data['is_featured']) SonarDeskRelease::query()->update(['is_featured' => false]); if ($request->hasFile('package')) { if ($release->file_path) Storage::disk('local')->delete($release->file_path); $file = $request->file('package'); $path = $file->store('sonardesk/releases', 'local'); $data['file_path'] = $path; $data['original_filename'] = $file->getClientOriginalName(); $data['file_size'] = $file->getSize(); $data['sha256'] = hash_file('sha256', $file->getRealPath()); } unset($data['package']); $data['published_at'] = $data['is_published'] ? ($release->published_at ?: now()) : null; $release->fill($data)->save(); } }