Skip to content

Switch all models to ULID primary keys#17

Merged
DGINXREAL merged 5 commits into
mainfrom
feat/ulid-primary-keys
Jun 18, 2026
Merged

Switch all models to ULID primary keys#17
DGINXREAL merged 5 commits into
mainfrom
feat/ulid-primary-keys

Conversation

@DGINXREAL

Copy link
Copy Markdown
Member

Summary

Migrates every owned Eloquent model from auto-increment bigint primary keys to time-ordered ULID (char(26)) primary keys.

Why ULID, not UUIDv4: the goal was a single non-sequential identifier type plus headroom for higher request volume. Random UUIDv4 PKs would hurt under load — they fragment the index on the high-volume statistics table. ULIDs are time-ordered, so inserts stay sequential like auto-increment while remaining globally unique and non-guessable.

Data was disposable (test data only), so the rollout is migrate:fresh --seed — no data transform.

Changes

  • Migrations rewritten in place to be ULID-native: ulid('id')->primary() and foreignUlid(...) for every FK. Cascade/soft-delete behavior and composite pivot keys (project_user, pixel_url) preserved.
  • ModelsHasUlids added to all 8 owned models (User, Project, Domain, Url, QrCode, Pixel, Statistic, ProjectInvitation).
  • Backend — ~30 int-typed id params widened to string across controllers, RecordClickStatisticJob, StatisticsAggregator, and ReportDataService; domain_id/url_id validation rules changed integerulid. Genuinely-numeric values ($days, $limit, smtp_port, logo_size, type/status, count return types) left untouched.
  • Frontend — entity id/*_id/pixel_ids TypeScript types widened from number to string.
  • Demo commanddemo:setup fixed to resolve projects by name instead of hardcoded integer ids.

Out of scope (intentionally untouched)

  • settings (spatie-settings) and framework cache/jobs/password_reset_tokens tables stay on bigint id().

Verification

  • Full backend suite: 142 tests / 618 assertions passing, pristine.
  • npm run build (tsc + Vite): clean, 3192 modules.
  • Programmatic e2e smoke: ULID keys + getIncrementing()=false everywhere; redirect returns 302; RecordClickStatisticJob writes a Statistic with ULID FKs; QR attach works.
  • demo:setup --force: exit 0, seeds 3 users / 2 projects / 2 domains / 40 URLs with valid ULID FKs.
  • Final whole-branch review (independent): ready to merge, no Critical/Important findings.

Notes for deploy

  • migrate:fresh --seed rebuilds the sessions table → users re-authenticate.
  • Drain the queue before deploy: in-flight RecordClickStatisticJob payloads with old integer ids would fail against the new string constructor.
  • Still unverified: pure-UI visual click-through (backend logic verified programmatically).

🤖 Generated with Claude Code

DGINXREAL and others added 5 commits June 18, 2026 12:08
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The demo:setup command hardcoded project_id 1/2 and ->named(1,1)/(2,2),
which were valid under auto-increment but break under ULID primary keys
(FK violation inserting domains). Resolve projects by name and derive
their domain id, mirroring the existing getOwnerOneUser() lookup pattern.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@DGINXREAL DGINXREAL requested a review from noidee-dev as a code owner June 18, 2026 13:04
@DGINXREAL DGINXREAL merged commit fe42ed8 into main Jun 18, 2026
3 checks passed
@DGINXREAL DGINXREAL deleted the feat/ulid-primary-keys branch June 18, 2026 13:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant