Commit Graph
23 Commits
Author SHA1 Message Date
isaaccladandClaude f2019dbb2a link: take click counters off the hot row to stop deadlocks
Deploy Ladill Link / deploy (push) Successful in 1m18s
Every click ran three UPDATEs against the same short_links row plus one against
the owner's link_wallets row, all inside one transaction. Two consequences:

- A popular link serialised its entire traffic through a single row lock.
- Locking two rows per transaction meant concurrent clicks on different links of
  the same owner could take them in opposite orders, so MySQL deadlocked.

Production logged 152 deadlocks, 146 of them on one row, surfacing as HTTP 500s to
real visitors. With a link now being handed to a very large audience, this is the
next thing that falls over, and it is independent of page weight.

Counters are now buffered and flushed instead of written inline:

- link_clicks stays the source of truth. It is insert-only, so it has no
  contention no matter how popular a link gets.
- Increments accumulate in Redis hashes and drain via link:flush-click-counters,
  scheduled every minute. The database sees one UPDATE per link per flush instead
  of four per click.
- Draining reads and deletes each hash atomically, so a click landing mid-flush is
  counted in that batch or the next, never dropped.
- --recount rebuilds totals from link_clicks after a Redis loss.

If the buffer is unavailable the recorder writes through to the database, keeping
today's behaviour (and today's contention) rather than losing counts. Nothing in
the click path may break the redirect the visitor actually came for, so the whole
recorder is wrapped and failures are logged.

Storage sits behind ClickCounterStore so the buffered path is covered by tests
rather than silently falling back to write-through when no Redis is present.

Also adds the (short_link_id, ip_hash, clicked_at) index. The unique-click check
filters on ip_hash but only (short_link_id, clicked_at) was indexed, so every
click scanned all of that link's rows in the dedupe window — cost growing with the
link's own popularity, the worst possible shape for a link going viral.

Tests: 11 new. The load-bearing one asserts the request path issues no UPDATE
against short_links or link_wallets at all.

Pre-existing suite failures go from 13 to 9; none of the remainder are related.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-25 11:38:12 +00:00
isaacclad f45fdcd159 Add Campaigns module for grouping short links.
Deploy Ladill Link / deploy (push) Successful in 1m26s
Let accounts organise links by promo or launch, attach or detach links, and view combined click totals from the sidebar.
2026-07-16 20:43:11 +00:00
isaacclad 86cac606e0 Surface destination edits as the core dynamic short-link feature.
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.
2026-07-16 20:12:14 +00:00
isaacclad d45f4b5c58 Make Ladill Link short URLs fully dynamic at click time.
Deploy Ladill Link / deploy (push) Successful in 1m55s
Forward query strings and path suffixes to the live destination, resolve public hosts from config/custom domains instead of hardcoding ladl.link, and keep proxy Location rewrites on the visitor host.
2026-07-16 20:05:43 +00:00
isaaccladandCursor 99a0cdb013 Add hero section to My Links index with wallet stats and balance gate.
Deploy Ladill Link / deploy (push) Successful in 41s
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-04 23:53:41 +00:00
isaaccladandCursor 5e87debe32 Clarify GeoIP update env setup for cached config deployments.
Deploy Ladill Link / deploy (push) Successful in 41s
Point operators at shared/.env and config:clear when MaxMind credentials are missing.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-04 23:45:24 +00:00
isaaccladandCursor 344bf76391 Add GeoIP country fallback for link click analytics.
Deploy Ladill Link / deploy (push) Successful in 39s
Resolve countries from CDN headers first, then MaxMind GeoLite2 via visitor IP, with an artisan command to download and refresh the database.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-04 23:36:01 +00:00
isaaccladandCursor 01371663fb Proxy speaker portal paths on ladl.link to Events.
Deploy Ladill Link / deploy (push) Successful in 43s
Route /{slug}/speaker/{token} through the Events app so speaker invite links resolve instead of hitting the account QR hub or a broken proxy patch.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-03 17:47:27 +00:00
isaaccladandCursor 4dedcf84f6 Forward JSON registration POST bodies through the ladl.link proxy.
Deploy Ladill Link / deploy (push) Successful in 41s
Event signup from ladl.link sends application/json; re-encoding as form
data dropped the payload and caused failed registrations to redirect to
ladl.link/undefined.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-03 14:06:36 +00:00
isaaccladandCursor 317a2f1981 Route event registration paths directly to Events so confirmation pages resolve.
Deploy Ladill Link / deploy (push) Successful in 1m30s
ladl.link was proxying registered/* through ladill.com first, where registrations
do not exist, causing 404s after successful event signup.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-03 13:44:51 +00:00
isaaccladandCursor a79fac78a5 Add reusable copy-to-clipboard button for Link UI.
Deploy Ladill Link / deploy (push) Successful in 52s
Share clipboard helper and Alpine copyButton component across link views.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 08:25:27 +00:00
isaaccladandCursor 1742fb859f Fall back to Events when platform QR proxy returns 404.
Deploy Ladill Link / deploy (push) Successful in 48s
Event pages like sales-webinar live on events.ladill.com, but ladl.link
only proxied to ladill.com/q/* — fix by retrying events.ladill.com when
the platform hub has no matching code.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 08:19:29 +00:00
isaaccladandCursor 8c73f157be Throttle platform SSO session checks to cut per-request auth latency.
Deploy Ladill Link / deploy (push) Successful in 1m11s
Verify auth.ladill.com at most once every 90s; recommend Redis for session/cache in .env.example.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 16:51:35 +00:00
isaaccladandCursor c5e92012ff Fix ladl.link transfer QR redirect loop causing 500 errors.
Deploy Ladill Link / deploy (push) Successful in 1m1s
Proxy transfer paths directly to transfer.ladill.com and stop following ladl.link redirects server-side so recipient links resolve.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 20:55:48 +00:00
isaaccladandCursor 0e5e0f6ca5 Add device, platform, and country analytics to link show page.
Deploy Ladill Link / deploy (push) Successful in 32s
Record parsed user-agent and geo data on clicks, then surface breakdowns and richer recent-click context on per-link and account analytics views.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-27 19:32:40 +00:00
isaaccladandCursor 7cdb1ef409 Redirect legacy /q/* URLs to canonical ladl.link addresses.
Deploy Ladill Link / deploy (push) Successful in 27s
External /q requests 301/307 to ladl.link; internal X-Ladill-Internal requests still serve content for platform proxying.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-27 17:19:04 +00:00
isaaccladandCursor 5982165128 Fix ladl.link 404s for QR ecosystem slugs.
Deploy Ladill Link / deploy (push) Successful in 31s
Only direct Link redirects use destination_url; catalog QR slugs proxy through the platform resolver instead of malformed summary URLs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-27 15:26:27 +00:00
isaaccladandCursor 29993040bd Read Afia AI credentials from platform admin settings.
Deploy Ladill Link / deploy (push) Successful in 58s
Link Afia now resolves provider, model, and API keys from ladilldb.platform_settings like the monolith, with .env fallbacks for local dev.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-27 14:34:43 +00:00
isaaccladandCursor 5a3631cd2e Fix Afia AI assistant for Ladill Link.
Deploy Ladill Link / deploy (push) Successful in 31s
Wire the chat route and Link-scoped system prompt, include the Afia panel in the main layout, and point the UI at link.afia.chat instead of the copied QR Plus route.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-27 14:31:00 +00:00
isaaccladandCursor f1fe749934 Aggregate ladl.link slugs from all platform apps in the link list.
Deploy Ladill Link / deploy (push) Successful in 29s
Sync QR and storefront short codes from sibling apps via a platform catalog API so My Links shows every ladl.link URL in one place.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-27 13:26:16 +00:00
isaaccladandCursor 9516fcb9f3 Expand Link app with analytics, settings, and custom domains.
Deploy Ladill Link / deploy (push) Successful in 37s
Add Bitly-style branded domain support via Ladill Domains SSL API,
account analytics dashboard, settings page with default domain picker,
and fix SSO/dashboard issues from QR Plus template leftovers.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-27 12:16:34 +00:00
isaaccladandCursor d9c91ad7d8 Add Ladill Link URL shortener on ladl.link.
Management UI at link.ladill.com with GHS 0.05 per link wallet billing,
click analytics, and legacy fallback to ladill.com/q for QR ecosystem codes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-27 10:58:41 +00:00
isaacclad 04e4f6ab51 Bootstrap Ladill Link from QR Plus extract template. 2026-06-27 10:54:31 +00:00