Deploy Ladill Link / deploy (push) Successful in 1m40s
Move destination editing to the top of the link page, clarify that the short URL never changes, prevent caches from pinning old redirects, and test that later destination updates take effect immediately.
154 lines
5.3 KiB
PHP
154 lines
5.3 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Link;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\ShortLink;
|
|
use App\Services\Link\LinkAnalyticsService;
|
|
use App\Services\Link\LinkBillingService;
|
|
use App\Services\Link\LinkCatalogSyncService;
|
|
use App\Services\Link\LinkManagerService;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\View\View;
|
|
use RuntimeException;
|
|
|
|
class LinkController extends Controller
|
|
{
|
|
public function __construct(
|
|
private LinkManagerService $links,
|
|
private LinkBillingService $billing,
|
|
private LinkCatalogSyncService $catalogSync,
|
|
private LinkAnalyticsService $analytics,
|
|
) {}
|
|
|
|
public function index(Request $request): View
|
|
{
|
|
$account = ladill_account();
|
|
$this->catalogSync->syncForUser($account);
|
|
|
|
$links = ShortLink::query()
|
|
->where('user_id', $account->id)
|
|
->latest()
|
|
->paginate(20);
|
|
|
|
$wallet = $account->getOrCreateLinkWallet();
|
|
|
|
return view('links.index', [
|
|
'links' => $links,
|
|
'wallet' => $wallet,
|
|
'balance' => $this->billing->balanceCedis($account),
|
|
'canCreate' => $this->billing->canCreate($account),
|
|
'pricePerLink' => \App\Models\LinkWallet::pricePerLink(),
|
|
'topupUrl' => ladill_account_url('wallet'),
|
|
'publicHost' => parse_url($account->publicLinkBaseUrl(), PHP_URL_HOST) ?: ShortLink::publicHost(),
|
|
'publicBaseUrl' => $account->publicLinkBaseUrl(),
|
|
]);
|
|
}
|
|
|
|
public function create(): View
|
|
{
|
|
$account = ladill_account();
|
|
|
|
return view('links.create', [
|
|
'canCreate' => $this->billing->canCreate($account),
|
|
'pricePerLink' => \App\Models\LinkWallet::pricePerLink(),
|
|
'publicHost' => parse_url($account->publicLinkBaseUrl(), PHP_URL_HOST) ?: ShortLink::publicHost(),
|
|
'publicBaseUrl' => $account->publicLinkBaseUrl(),
|
|
]);
|
|
}
|
|
|
|
public function store(Request $request): RedirectResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'destination_url' => ['required', 'string', 'max:2048'],
|
|
'label' => ['nullable', 'string', 'max:120'],
|
|
'custom_slug' => ['nullable', 'string', 'max:20', 'regex:/^[a-z0-9][a-z0-9-]{1,18}[a-z0-9]$/'],
|
|
'expires_at' => ['nullable', 'date', 'after:now'],
|
|
]);
|
|
|
|
try {
|
|
$link = $this->links->create(ladill_account(), $validated);
|
|
} catch (RuntimeException $e) {
|
|
return back()->withInput()->withErrors(['balance' => $e->getMessage()]);
|
|
}
|
|
|
|
return redirect()
|
|
->route('user.links.show', $link)
|
|
->with('success', 'Short link created.');
|
|
}
|
|
|
|
public function show(ShortLink $link): View
|
|
{
|
|
$this->authorizeLink($link);
|
|
|
|
return view('links.show', [
|
|
'link' => $link,
|
|
'summary' => $this->analytics->summaryForLink($link),
|
|
'dailyClicks' => $this->analytics->dailyClicksForLink($link, 30),
|
|
'devices' => $this->analytics->breakdownForLink($link, 'device_type'),
|
|
'browsers' => $this->analytics->breakdownForLink($link, 'browser'),
|
|
'platforms' => $this->analytics->breakdownForLink($link, 'os'),
|
|
'countries' => $this->analytics->breakdownForLink($link, 'country'),
|
|
'referrers' => $this->analytics->referrersForLink($link),
|
|
'recentClicks' => $this->analytics->recentClicksForLink($link),
|
|
]);
|
|
}
|
|
|
|
public function update(Request $request, ShortLink $link): RedirectResponse
|
|
{
|
|
$this->authorizeLink($link);
|
|
|
|
if (! $link->is_managed_here) {
|
|
return back()->withErrors(['link' => 'This link is managed in '.$link->sourceAppLabel().'.']);
|
|
}
|
|
|
|
$validated = $request->validate([
|
|
'destination_url' => ['sometimes', 'required', 'string', 'max:2048'],
|
|
'label' => ['nullable', 'string', 'max:120'],
|
|
'is_active' => ['sometimes', 'boolean'],
|
|
'expires_at' => ['nullable', 'date'],
|
|
]);
|
|
|
|
try {
|
|
$this->links->update($link, $validated);
|
|
} catch (RuntimeException $e) {
|
|
return back()->withInput()->withErrors(['destination_url' => $e->getMessage()]);
|
|
}
|
|
|
|
$message = isset($validated['destination_url'])
|
|
? 'Destination updated. Your short link is unchanged — new clicks go to the new URL.'
|
|
: 'Link updated.';
|
|
|
|
return back()->with('success', $message);
|
|
}
|
|
|
|
public function destroy(ShortLink $link): RedirectResponse
|
|
{
|
|
$this->authorizeLink($link);
|
|
|
|
if (! $link->is_managed_here) {
|
|
return back()->withErrors(['link' => 'This link is managed in '.$link->sourceAppLabel().'.']);
|
|
}
|
|
|
|
$link->delete();
|
|
|
|
return redirect()->route('user.links.index')->with('success', 'Link deleted.');
|
|
}
|
|
|
|
public function checkSlug(Request $request): \Illuminate\Http\JsonResponse
|
|
{
|
|
$slug = strtolower((string) $request->query('slug', ''));
|
|
$taken = $slug !== '' && ShortLink::where('slug', $slug)->exists();
|
|
|
|
return response()->json(['available' => ! $taken]);
|
|
}
|
|
|
|
private function authorizeLink(ShortLink $link): void
|
|
{
|
|
if ($link->user_id !== ladill_account()->id) {
|
|
abort(403);
|
|
}
|
|
}
|
|
}
|