54 lines
5.5 KiB
PHP
54 lines
5.5 KiB
PHP
<?php
|
|
namespace App\Livewire\Admin;
|
|
use Livewire\Component;
|
|
use Livewire\WithFileUploads;
|
|
use Livewire\Attributes\Locked;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Storage;
|
|
class Cms extends Component {
|
|
use WithFileUploads;
|
|
public string $section='pages';public array $form=[];public $upload;
|
|
#[Locked] public ?int $editing=null;
|
|
private const TABLES=['pages','releases','users','purchases','licenses','tickets','privacy_requests','consents','audit_logs','settings'];
|
|
public function boot(): void {abort_unless(auth()->user()?->is_admin && auth()->user()->two_factor_confirmed_at,403);}
|
|
private function section(): string {abort_unless(in_array($this->section,self::TABLES),404);return $this->section;}
|
|
public function newRecord(): void {$this->reset('form','editing','upload');}
|
|
public function updatedSection(): void {$this->reset('form','editing','upload');}
|
|
private function audit(string $action,string $resource): void {DB::table('audit_logs')->insert(['user_id'=>auth()->id(),'action'=>$action,'resource'=>$resource,'created_at'=>now(),'updated_at'=>now()]);}
|
|
public function edit(int $id): void {
|
|
$section=$this->section();abort_unless(in_array($section,['pages','releases']),403);
|
|
$row=DB::table($section)->find($id);abort_unless($row,404);$this->editing=$id;
|
|
$this->form=array_intersect_key((array)$row,array_flip($section==='pages'?['slug','title','body','description','published']:['version','channel','platform','notes','published']));
|
|
}
|
|
public function save(): void {
|
|
$section=$this->section();abort_unless(in_array($section,['pages','releases']),403);
|
|
if($section==='pages'){$data=$this->validate(['form.slug'=>'required|alpha_dash|max:100|unique:pages,slug,'.($this->editing??'NULL'),'form.title'=>'required|string|max:200','form.body'=>'required|string|max:100000','form.description'=>'nullable|string|max:255','form.published'=>'boolean'])['form'];}
|
|
else {
|
|
$data=$this->validate(['form.version'=>'required|regex:/^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9.]+)?$/|max:50|unique:releases,version,'.($this->editing??'NULL'),'form.channel'=>'required|in:stable,beta','form.platform'=>'required|string|max:50','form.notes'=>'required|string|max:20000','form.published'=>'boolean'])['form'];
|
|
$this->validate(['upload'=>($this->editing?'nullable':'required').'|file|extensions:zip,dmg,pkg|max:1048576']);
|
|
if($this->upload){$path=$this->upload->store('releases','local');$data+=['path'=>$path,'sha256'=>hash_file('sha256',Storage::disk('local')->path($path)),'bytes'=>Storage::disk('local')->size($path)];}
|
|
}
|
|
$data['published']=$data['published']??false;$data['updated_at']=now();
|
|
if($this->editing){DB::table($section)->where('id',$this->editing)->update($data);$id=$this->editing;}else{$data['created_at']=now();$id=DB::table($section)->insertGetId($data);}
|
|
$this->audit('saved',$section.':'.$id);$this->reset('form','editing','upload');session()->flash('status','Saved successfully.');
|
|
}
|
|
public function revoke(int $id): void {abort_unless($this->section()==='licenses',403);DB::table('licenses')->where('id',$id)->update(['status'=>'revoked','updated_at'=>now()]);$this->audit('revoked','license:'.$id);}
|
|
public function saveSettings(): void {
|
|
abort_unless($this->section()==='settings',403);$v=$this->validate(['form.headline'=>'required|string|max:120','form.tagline'=>'required|string|max:500','form.price_label'=>'required|string|max:100'])['form'];foreach($v as $key=>$value){DB::table('settings')->updateOrInsert(['key'=>$key],['value'=>$value]);}$this->audit('saved','settings');session()->flash('status','Settings saved.');
|
|
}
|
|
public function fulfillDeletion(int $id): void {
|
|
abort_unless($this->section()==='privacy_requests',403);
|
|
DB::transaction(function()use($id){$request=DB::table('privacy_requests')->where('id',$id)->lockForUpdate()->first();abort_unless($request && $request->type==='deletion' && $request->status==='pending',422);abort_if($request->user_id===auth()->id(),422,'Cannot erase your own administrator account.');
|
|
$user=\App\Models\User::findOrFail($request->user_id);abort_if($user->is_admin,422,'Remove administrator access before erasure.');
|
|
DB::table('licenses')->where('user_id',$user->id)->update(['status'=>'revoked','updated_at'=>now()]);DB::table('activations')->whereIn('license_id',DB::table('licenses')->where('user_id',$user->id)->select('id'))->delete();
|
|
DB::table('tickets')->where('user_id',$user->id)->delete();DB::table('ticket_messages')->where('user_id',$user->id)->delete();DB::table('consents')->where('user_id',$user->id)->delete();DB::table('sessions')->where('user_id',$user->id)->delete();DB::table('password_reset_tokens')->where('email',$user->email)->delete();
|
|
$user->forceFill(['name'=>'Deleted customer','email'=>'deleted-'.$user->id.'@invalid.example','password'=>bin2hex(random_bytes(32)),'remember_token'=>null,'account_token'=>null,'two_factor_secret'=>null,'two_factor_recovery_codes'=>null])->save();
|
|
DB::table('privacy_requests')->where('id',$id)->update(['status'=>'fulfilled','updated_at'=>now()]);$this->audit('privacy erasure fulfilled; billing records retained','user:'.$user->id);
|
|
});
|
|
}
|
|
public function render() {
|
|
$section=$this->section();$query=DB::table($section);if($section==='users'){$query->select('id','name','email','is_admin','email_verified_at');}elseif($section==='licenses'){$query->select('id','user_id','purchase_id','status','device_limit');}
|
|
return view('livewire.admin.cms',['sections'=>self::TABLES,'rows'=>$query->orderBy($section==='settings'?'key':'id','desc')->limit(100)->get()]);
|
|
}
|
|
}
|