authorize('dpa.manage'); } public function upload(): void { $this->authorize('dpa.manage'); $data = $this->validate([ 'version' => 'required|string|max:64|unique:dpa_versions,version', 'note' => 'nullable|string|max:2000', // PDF only, and a real size limit: this is handed to customers as // the contract between us, not as an editable file. 'agreement' => 'required|file|mimes:pdf|max:10240', 'measures' => 'nullable|file|mimes:pdf|max:10240', ]); // The private DISK. An agreement is not a public asset, and a guessable // URL to one would be a list of who our customers are — the root of this // disk is outside the document root and nothing here is reachable except // through the two routes that check who is asking. // // The visibility below is the file MODE, not the web: 0755/0644 rather // than 0700/0600, so a document written by one process (an artisan run // as root) stays readable by the other (PHP-FPM as www-data). Getting // that wrong produced a directory nothing could enter and a page whose // every link 404'd silently. $version = DpaVersion::query()->create([ 'version' => $data['version'], 'note' => $data['note'] ?: null, 'agreement_path' => $this->agreement->store('dpa', 'local', 'public'), 'measures_path' => $this->measures?->store('dpa', 'local', 'public'), 'created_by' => Auth::guard('operator')->id(), ]); $this->reset('version', 'note', 'agreement', 'measures'); $this->dispatch('notify', message: __('dpa_admin.uploaded', ['version' => $version->version])); } /** * Put a version in force. * * Deliberately separate from the upload: every customer who had accepted the * previous one is outstanding again from this moment, and that is not * something to do by dropping a file on a form. */ #[On('dpa-publish-confirmed')] public function publish(string $uuid): void { $this->authorize('dpa.manage'); $version = DpaVersion::query()->where('uuid', $uuid)->firstOrFail(); if ($version->isPublished()) { return; } $version->update(['published_at' => now()]); $this->dispatch('notify', message: __('dpa_admin.published', ['version' => $version->version])); } public function render() { $this->authorize('dpa.manage'); $current = app(ProcessingAgreement::class)->current(); // How many customers are outstanding on the version in force. Counted // against customers who are actually still customers — a closed record // is not somebody we are waiting on. $customers = Customer::query()->whereNull('closed_at')->count(); $accepted = $current === null ? 0 : $current->acceptances()->count(); return view('livewire.admin.processing-agreements', [ 'versions' => DpaVersion::query()->withCount('acceptances')->latest('id')->get(), 'current' => $current, 'customers' => $customers, 'accepted' => $accepted, 'outstanding' => max(0, $customers - $accepted), ]); } /** Whether a stored file is actually there, for the list. */ public function exists(?string $path): bool { return $path !== null && Storage::disk('local')->exists($path); } }