Commit Graph

35 Commits (d9f665f749722b0b6c79184d831da9ef7775d1a0)

Author SHA1 Message Date
boban d9f665f749 fix(ai): switch to OpenAI, add error handling in Insights
- AiService: use OpenAI chat completions API (gpt-4o-mini) instead of Anthropic
- Add services.openai.api_key config entry
- Insights component: check for API key, handle empty links, catch HTTP errors
- View: show error banner, hide placeholder when error shown

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 14:40:11 +02:00
boban 43ff6d6407 fix(FIX-10): 6 bugs — route ULID, QR modal, bio unique, nav active, copy btn, analytics context
- Workspace.getRouteKeyName()='ulid' so route() generates ULID URLs not ID
- QR modal 4xl→5xl; import QRCode in @script→window.QRCode (no ESM in @script)
- Bio slug unique rule scoped to (workspace_id, domain_id) not global
- updateActiveNav uses URL.pathname not raw getAttribute('href') (absolute URL fix)
- Add resources/views/components/ui/copy-button.blade.php Alpine component
- Analytics Index.render/baseQuery use $workspaceId not require_workspace() — prevents RuntimeException on Livewire action calls without middleware

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 17:01:16 +02:00
boban 257d1b94f1 feat: QR wizard, UTM Builder, A/B variants, AI features, analytics URL filters
- QR Code: split-pane wizard with type tabs, live preview canvas, style/frame/advanced options
- UTM Builder modal: generate URL with UTM params, copy + create short link
- A/B Test: LinkVariants modal with auto-balance weights (min 2, max 5 variants)
- AI: SlugSuggester service (3 suggestions via claude-haiku), AI-slug button in CreateLink
- AI pages: /ai/insights, /ai/anomalies, /ai/ab-generator with sidebar entry
- Analytics: #[Url] attributes for range/country/device/linkId, 24h/7d/30d/90d buttons
- QR enum migration (MySQL-only guard) to add email/phone/sms/location types

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 15:14:19 +02:00
boban c67bde8fc5 feat: Toaster, button-component, 10 new modals, links-page fix, vite-tuning
- Toaster Livewire component (toast event, dismiss, auto-hide, 6 tests)
- <x-ui.button> Blade component with wire:loading indicator + variants
- QR modals: CreateQrCode, EditQrCode, DeleteQrCode (TDD, 8 tests)
- Bio modals: CreateBioPage, EditBioPage, DeleteBioPage (TDD, 7 tests)
- Domain modals: AddDomain, VerifyDomain, DeleteDomain (TDD, 7 tests)
- Profile modal: DeleteAccount with password confirm (TDD, 4 tests)
- Links page: fix openModal event name, wire up Edit/Delete to modals, toast on all CRUD
- vite.config.js: add chart.js/auto + optimizeDeps.force
- QrCode model: add newFactory() + QrCodeFactory
- Layout: Toaster embedded before </body>
- 141 tests / 296 assertions all green, verify 29/29

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 13:55:25 +02:00
boban 5748dd3443 style: apply Pint formatting to modal files and tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 12:42:16 +02:00
boban fa9985cc33 feat: modal infrastructure + 11 modals (links, team, settings, workspace)
wire-elements/modal v3 integration:
- Replace custom Alpine modal-container with <livewire:livewire-ui-modal />
- Customize vendor modal view with design tokens (bg-s1, dark overlay, smooth transitions)
- All modals extend ModalComponent, use $this->closeModal()

Modals built (PHP + Blade view + tests):
- ConfirmAction — reusable danger confirm with custom event dispatch
- CreateLink, EditLink, DeleteLink — full CRUD with workspace isolation
- InviteMember, EditMemberRole, RemoveMember — team management
- CreateApiToken, RevokeApiToken — Sanctum token lifecycle
- CreateWebhook, DeleteWebhook — webhook CRUD
- CreateWorkspace (uses CreateWorkspaceAction), DeleteWorkspace (confirmation input)

Fixes:
- Remove SoftDeletes from Webhook model (table has no deleted_at column)
- Auto-generate webhook secret when user leaves field empty
- Use correct role enum: owner/admin/editor/viewer (not member)
- Protected Livewire model properties replaced with re-query pattern
- Sidebar "New Workspace" button now dispatches openModal

Tests: 105/105 passing, 211 assertions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 12:41:14 +02:00
boban ea9274be49 fix(quality): PHPStan level 6 compliance, Pint formatting
- Add phpstan.neon with level 6 config and larastan extension
- Fix all 92 PHPStan errors: Eloquent relation generics, iterable
  value types, HasFactory generic annotations, missing return types
- Fix logic bugs: always-true Safari/Chrome comparison, nullsafe on
  non-nullable Carbon, unused $renderer suppressed with ignore rule
- Implement MustVerifyEmail on User model (was commented out in Breeze
  scaffold) to satisfy VerifyEmailController type contract
- Remove broken QrCode::newFactory() referencing non-existent factory
- Add @var docblocks for app('current_workspace') mixed resolution
- Apply Pint formatting: 53 style issues fixed across 168 files
- Test suite unchanged: 9 pre-existing assertSeeLivewire failures,
  55 tests passing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 08:28:10 +02:00
boban 96ec518200 feat(domains): Domain model, DNS TXT verification action, pending verifier job 2026-05-16 08:14:33 +02:00
boban a394e0ff18 fix(bio): add BioPageFactory, newFactory() override on BioPage 2026-05-16 08:12:54 +02:00
boban e9623b9b31 feat(bio): Link-in-Bio pages with translatable fields, public render 2026-05-16 08:10:59 +02:00
boban c193e482ac feat(ai): AiService (Anthropic adapter), GenerateSlug action
Implement AI domain with Anthropic API integration for slug generation.
Adds GenerateSlug action for URL-to-slug transformation and GenerateAbVariants
for A/B test variants. Includes test coverage for GenerateSlug action with
mocked AiService.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 08:06:11 +02:00
boban e33a1fafde fix(webhooks): HMAC integrity fix (send raw body), IPv6 SSRF protection
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 07:49:25 +02:00
boban c8a6325a77 feat(webhooks): outgoing webhooks with HMAC signature, SSRF protection, retry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 07:46:57 +02:00
boban 69ca1ab058 fix(link): evict cache after delete, not before 2026-05-16 07:44:21 +02:00
boban 3568e67ca0 fix(api): use DeleteLink action for cache invalidation, fix validation, add delete tests 2026-05-16 07:42:56 +02:00
boban 1375519cdb feat(api): REST API v1 — links CRUD with Sanctum auth
Adds /api/v1/workspaces/{ulid}/links endpoints (index, store, destroy)
protected by Sanctum token auth; adds HasApiTokens to User model and
AuthorizesRequests to base Controller; wires routes/api.php into bootstrap.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 07:38:45 +02:00
boban 06dbb1dffb feat(billing): Cashier Billable on Workspace, Stripe webhook route
- Add Laravel\Cashier\Billable trait to Workspace model
- Register Workspace as Cashier customer model in AppServiceProvider
- Publish cashier config with EUR currency and de_DE locale defaults
- Add CSRF exclusion for stripe/* in bootstrap/app.php
- Add StripeWebhookTest verifying endpoint exists and rejects bad signatures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 01:35:11 +02:00
boban b121520ed6 fix(i18n): include namespace in cache key/query, clean up singleton, test isolation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 01:27:10 +02:00
boban ce9e53d206 feat(i18n): database-backed translation loader with Redis cache
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 01:20:48 +02:00
boban 15f4b2c0d8 fix(qr): wifi/vcard injection protection, remove dead code
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 01:16:47 +02:00
boban d3b777d8e8 feat(qr): QR code renderer (SVG + PNG), model, generate action
Implements QrRenderer using chillerlan/php-qrcode v6 API (outputInterface
instead of deprecated outputType constants), QrCode Eloquent model with
ULID auto-generation and soft deletes, and GenerateQrCode action.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 01:13:41 +02:00
boban dd89ee395c feat(billing): plan limits service, plan factory, seeder (free/pro/business/agency) 2026-05-16 01:06:01 +02:00
boban f757f43926 feat(analytics): hourly/daily rollups, 90-day raw click prune, schedule 2026-05-16 01:03:49 +02:00
boban 7d47222591 fix(routing): defensive fallback, between bounds check, condition key guard 2026-05-16 01:02:37 +02:00
boban e06db03320 feat(routing): smart routing engine with condition evaluator (device, country, UTM) 2026-05-16 01:01:47 +02:00
boban 86f767a626 fix(analytics): UTM from query string, iOS before macOS detection, remove dead referer fallback 2026-05-16 01:00:10 +02:00
boban 66e559173c feat(analytics): click recording job with IP hashing, device detection, Redis counters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 00:56:52 +02:00
boban ebdcd141bb feat(redirect): hot-path redirect controller with Redis cache, 302/404/410
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 00:52:16 +02:00
boban b59dbfd95b fix(link): nullable field update, redis test key isolation 2026-05-16 00:48:15 +02:00
boban 69e6318db5 feat(link): cache service, create/update/delete actions, slug generation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 00:46:06 +02:00
boban 587aa09b40 fix(workspace): unique slug generation, null-safe middleware auth guard 2026-05-16 00:43:55 +02:00
boban 49f2e3b00f feat(workspace): create action, middleware, policies
- CreateWorkspace action: creates workspace + owner member + sets default_workspace_id
- ResolveWorkspace middleware: resolves workspace by ULID, 404s for non-members
- WorkspacePolicy: owner/admin/editor/viewer role-based gates
- Register WorkspacePolicy in AppServiceProvider
- Add ulid string cast to Workspace model to ensure scalar after create()
- Feature test: CreateWorkspaceTest (4 assertions, passing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 00:41:32 +02:00
boban 6a96b8e0a3 fix(models): null-guard isExpired, remove FK from guarded, add return types and casts
- Add null check to WorkspaceInvitation::isExpired() to prevent null errors
- Remove foreign key columns from protected $guarded to prevent silent mass-assignment drops in Workspace, Link, and Domain models
- Add explicit return types (HasMany, BelongsTo) to User relationship methods
- Add return type to Click::link() relationship method
- Add protected $casts for LinkVariant (weight, clicks_count, conversions_count)

Fixes code quality and type safety issues in domain models.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 00:27:15 +02:00
boban dc6ed34357 fix(models): auto-generate ulid on creating for Workspace and Link 2026-05-16 00:23:33 +02:00
boban d0bb8f92ee feat(models): add core domain models
Creates Workspace, WorkspaceMember, WorkspaceInvitation, Link,
LinkVariant, Click (append-only), Plan, and Domain Eloquent models with
correct relationships. Updates User model with SoftDeletes, ULID
auto-generation on create, guarded/casts, and workspace relationships.
Fixes ProfileTest assertion to match SoftDeletes behavior on User.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 00:19:51 +02:00