DB::table('pages')->where('published', true)->where('slug', 'features')->first()]); } public function page(string $slug) { return view('page', ['page' => DB::table('pages')->where('slug', $slug)->where('published', true)->firstOrFail()]); } public function releases() { return view('releases', ['releases' => DB::table('releases')->where('published', true)->latest()->paginate(15)]); } public function account(Request $r) { if (! $r->user()->account_token) { $r->user()->forceFill(['account_token' => (string) Str::uuid()])->save(); } return view('account', ['licenses' => DB::table('licenses')->where('user_id', $r->user()->id)->get(), 'purchases' => DB::table('purchases')->where('user_id', $r->user()->id)->latest()->get(), 'releases' => DB::table('releases')->where('published', true)->latest()->get()]); } public function downloadLink(Request $r, int $release) { abort_unless(DB::table('licenses')->where('user_id', $r->user()->id)->where('status', 'active')->exists(), 403); abort_unless(DB::table('releases')->where('id', $release)->where('published', true)->exists(), 404); return redirect(URL::temporarySignedRoute('download.file', now()->addMinutes(5), ['release' => $release, 'user' => $r->user()->id])); } public function download(Request $r, int $release, int $user) { abort_unless($r->user()->id === $user && DB::table('licenses')->where('user_id', $user)->where('status', 'active')->exists(), 403); $file = DB::table('releases')->where('id', $release)->where('published', true)->firstOrFail(); abort_unless(Storage::disk('local')->exists($file->path), 404); return Storage::disk('local')->download($file->path, 'Tarnax-'.$file->version.'.'.pathinfo($file->path, PATHINFO_EXTENSION)); } public function consent(Request $r) { $v = $r->validate(['analytics' => 'required|boolean', 'marketing' => 'required|boolean']); $visitor = $r->cookie('tarnax_visitor'); if (! Str::isUuid($visitor ?? '')) { $visitor = (string) Str::uuid(); } DB::table('consents')->insert([...$v, 'visitor_id' => $visitor, 'user_id' => $r->user()?->id, 'policy_version' => config('tarnax.consent_version'), 'created_at' => now(), 'updated_at' => now()]); return response()->json(['saved' => true])->cookie('tarnax_visitor', $visitor, 525600, '/', null, $r->isSecure(), true, false, 'lax')->cookie('tarnax_consent', json_encode([...$v, 'version' => config('tarnax.consent_version')]), 525600, '/', null, $r->isSecure(), false, false, 'lax'); } public function export(Request $r) { $data = ['profile' => $r->user()->only('name', 'email', 'created_at'), 'purchases' => DB::table('purchases')->where('user_id', $r->user()->id)->get(), 'support' => DB::table('ticket_messages')->where('user_id', $r->user()->id)->get(), 'consent' => DB::table('consents')->where('user_id', $r->user()->id)->get(), 'privacy_requests' => DB::table('privacy_requests')->where('user_id', $r->user()->id)->get()]; return response()->streamDownload(fn () => print (json_encode($data, JSON_PRETTY_PRINT)), 'tarnax-personal-data.json', ['Content-Type' => 'application/json']); } public function deletion(Request $r) { DB::table('privacy_requests')->updateOrInsert(['user_id' => $r->user()->id, 'type' => 'deletion', 'status' => 'pending'], ['created_at' => now(), 'updated_at' => now()]); return back()->with('status', 'Deletion requested. Support will review retained billing records and respond within the applicable deadline.'); } public function apple(Request $r, AppleVerifier $verifier) { $v = $r->validate(['signed_transaction' => 'required|string|max:25000']); $claims = $verifier->verify($v['signed_transaction'], $r->user()->account_token ?? ''); DB::transaction(function () use ($r, $claims) { $id = 'apple:'.$claims['transactionId']; $purchase = DB::table('purchases')->where('transaction_id', $id)->lockForUpdate()->first(); abort_if($purchase && $purchase->user_id !== $r->user()->id, 403); abort_if($purchase && in_array($purchase->status, ['revoked', 'expired']), 403, 'Purchase is no longer valid.'); if (! $purchase) { DB::table('purchases')->insert(['user_id' => $r->user()->id, 'provider' => 'apple', 'transaction_id' => $id, 'status' => 'verified', 'provider_updated_at' => Carbon::createFromTimestampMs($claims['signedDate']), 'created_at' => now(), 'updated_at' => now()]); } }); return back()->with('status','Apple purchase verified. App Store entitlements remain separate from direct licenses.'); } }