Skip to content

Slugs as album ids#4123

Merged
ildyria merged 11 commits intomasterfrom
slug-as-album-id
Feb 28, 2026
Merged

Slugs as album ids#4123
ildyria merged 11 commits intomasterfrom
slug-as-album-id

Conversation

@ildyria
Copy link
Member

@ildyria ildyria commented Feb 27, 2026

Fixes #330
Fixes #1423

Summary by CodeRabbit

  • New Features

    • Albums (including tag albums) now support friendly URL slugs: set/clear, auto-generate from title, copy slug URL, and slug-based navigation alongside ID-based URLs. Slug visible to supporters in appropriate views.
  • Validation

    • Client/server validation prevents invalid, reserved, or duplicate slugs; supports excluding the current album when checking uniqueness.
  • Middleware

    • Requests accept slugs and resolve them to real IDs (including batch params), preserving passthrough for real IDs and special album types.
  • Database

    • Adds nullable unique slug column to albums.
  • Tests

    • Extensive unit and integration tests for validation, CRUD, and resolution.
  • Documentation

    • Spec, plan, tasks, roadmap, and decisions updated.
  • Translations

    • Slug UI text added across many languages.

@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d752594 and 1542b95.

📒 Files selected for processing (3)
  • app/Http/Requests/Album/UpdateAlbumRequest.php
  • app/Http/Requests/Album/UpdateTagAlbumRequest.php
  • resources/js/components/forms/album/AlbumProperties.vue
🚧 Files skipped from review as they are similar to previous changes (3)
  • resources/js/components/forms/album/AlbumProperties.vue
  • app/Http/Requests/Album/UpdateTagAlbumRequest.php
  • app/Http/Requests/Album/UpdateAlbumRequest.php

📝 Walkthrough

Walkthrough

Adds album slug support end-to-end: DB migration and model attribute, SlugRule validation, ResolveAlbumSlug middleware (registered and applied), request/resource changes to accept/persist/expose slugs, frontend UI/types, translations, tests, and documentation for Feature 019.

Changes

Cohort / File(s) Summary
Validation & Middleware
app/Rules/SlugRule.php, app/Http/Middleware/ResolveAlbumSlug.php, app/Http/Kernel.php
New SlugRule validates format, reserved words, and uniqueness (exclusion supported). ResolveAlbumSlug middleware converts album slugs to IDs across query/body/route/array params; Kernel registers middleware and alias.
Models & Request Constants
app/Models/BaseAlbumImpl.php, app/Models/Extensions/BaseAlbum.php, app/Contracts/Http/Requests/RequestAttribute.php
Adds slug to model attributes/casts and docblocks; introduces RequestAttribute::SLUG_ATTRIBUTE.
Requests & Resources
app/Http/Requests/Album/UpdateAlbumRequest.php, app/Http/Requests/Album/UpdateTagAlbumRequest.php, app/Http/Resources/Editable/EditableBaseAlbumResource.php, app/Http/Resources/Models/HeadAlbumResource.php, app/Http/Resources/Models/HeadTagAlbumResource.php
Requests accept and validate slug (StringRequireSupportRule + SlugRule) and persist slug to album base_class; resources expose nullable slug to supporters.
Database Migration
database/migrations/2026_02_27_000001_add_slug_to_base_albums.php
Adds nullable, unique slug varchar(250) column to base_albums with reversible migration.
Frontend UI & Types
resources/js/components/forms/album/AlbumProperties.vue, resources/js/services/album-service.ts, resources/js/lychee.d.ts
Adds slug input (generate/copy/preview), includes slug in save payloads, and extends TS types/resources with `slug: string
Routes
routes/web_v2.php
Applies resolve_album_slug middleware to gallery-related web routes (flow, gallery, frame, map, search) so route params can be slugs.
Translations
lang/{ar,bg,cz,de,el,en,es,fa,fr,hu,it,ja,nl,no,pl,pt,ru,sk,sv,vi,zh_CN,zh_TW}/gallery.php
Adds album.properties keys: slug, generate_slug, and copy_slug_url across locales.
Tests
tests/Unit/Rules/SlugRuleTest.php, tests/Unit/Middleware/ResolveAlbumSlugTest.php, tests/Unit/Http/AlbumSlugCrudTest.php, tests/Unit/Http/Requests/Album/UpdateAlbumRequestTest.php, tests/Unit/Http/Requests/Album/UpdateTagAlbumRequestTest.php
Adds/extends unit and integration tests for SlugRule, middleware resolution (including arrays and passthroughs), CRUD flows, validation rules, and request behavior.
Docs & Project Spec
docs/specs/.../019-friendly-urls/*, docs/specs/4-architecture/roadmap.md, docs/specs/4-architecture/open-questions.md, .github/copilot-instructions.md
Introduces full Feature 019 spec/plan/tasks, updates roadmap and resolved questions, and updates copilot instructions to reference PHP-based translation sources.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 I found a slug that hops into place,
It trades long IDs for a friendlier face.
Middleware burrows, rules keep it right,
Docs, tests, and translations join the light.
Hop — copy the URL, and share the delight!

🚥 Pre-merge checks | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ildyria ildyria marked this pull request as ready for review February 27, 2026 21:35
@ildyria ildyria requested a review from a team as a code owner February 27, 2026 21:35
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docs/specs/4-architecture/roadmap.md (1)

100-100: ⚠️ Potential issue | 🟡 Minor

Update the "Last updated" date to reflect actual changes.

The file shows Feature 019 was updated on 2026-02-28, but the footer still reads "2026-02-26". As per coding guidelines, update this to match the most recent modification date.

-*Last updated: 2026-02-26*
+*Last updated: 2026-02-28*
resources/js/components/forms/album/AlbumProperties.vue (1)

490-521: ⚠️ Potential issue | 🟡 Minor

Normalize slug input before sending update payloads.

The current slug.value === "" ? null : slug.value check misses whitespace-only values (e.g. " "), which are then sent to the API and fail validation unnecessarily.

💡 Proposed fix
+function normalizedSlug(): string | null {
+	const value = slug.value?.trim() ?? "";
+	return value === "" ? null : value;
+}
+
 function saveAlbum() {
 	const data: UpdateAbumData = {
 		album_id: albumId.value,
 		title: title.value,
-		slug: slug.value === "" ? null : slug.value,
+		slug: normalizedSlug(),
@@
 function saveTagAlbum() {
@@
 	const data: UpdateTagAlbumData = {
 		album_id: albumId.value,
 		title: title.value,
-		slug: slug.value === "" ? null : slug.value,
+		slug: normalizedSlug(),
🧹 Nitpick comments (5)
lang/no/gallery.php (1)

138-139: Localize new no strings to avoid mixed-language UI

Line 138 and Line 139 are still English in the Norwegian locale file, so users will see inconsistent language.

Proposed localization patch
-            'generate_slug' => 'Generate slug from title',
-            'copy_slug_url' => 'Copy URL to clipboard',
+            'generate_slug' => 'Generer slug fra tittel',
+            'copy_slug_url' => 'Kopier URL til utklippstavlen',
lang/ru/gallery.php (1)

136-138: New slug translation keys added; two strings remain in English.

Lines 137-138 contain English text ('Generate slug from title', 'Copy URL to clipboard') in this Russian translation file. Line 136 uses a hybrid approach with the technical term "Slug" followed by a Russian explanation, which is reasonable.

Given the existing pattern in this file (many other strings also remain untranslated), this is consistent with the current approach but may warrant localization in a future pass.

💬 Suggested Russian translations
             'slug' => 'Slug (дружественный URL)',
-            'generate_slug' => 'Generate slug from title',
-            'copy_slug_url' => 'Copy URL to clipboard',
+            'generate_slug' => 'Сгенерировать slug из названия',
+            'copy_slug_url' => 'Скопировать URL в буфер обмена',

Based on learnings: "The user ildyria does not prioritize strict localization consistency for new menu items in language files."

lang/it/gallery.php (1)

137-139: Translations are in English rather than Italian.

These new slug-related strings are in English. Consider translating them to Italian for consistency with the rest of the file:

  • 'Slug (friendly URL)''Slug (URL amichevole)'
  • 'Generate slug from title''Genera slug dal titolo'
  • 'Copy URL to clipboard''Copia URL negli appunti'
tests/Unit/Rules/SlugRuleTest.php (1)

31-43: Minor: Handle null values in assertion messages.

In assertPasses (line 35), concatenating a potentially null value will produce "Expected slug "" to pass..." which works but could be clearer. Consider using var_export($value, true) or a null-coalescing string for better debugging output.

💡 Optional improvement for clearer assertion messages
 private function assertPasses(SlugRule $rule, mixed $value): void
 {
     $failed = false;
     $rule->validate('slug', $value, function () use (&$failed): void { $failed = true; });
-    self::assertFalse($failed, 'Expected slug "' . $value . '" to pass but it failed.');
+    self::assertFalse($failed, 'Expected slug "' . var_export($value, true) . '" to pass but it failed.');
 }

 private function assertFails(SlugRule $rule, mixed $value): void
 {
     $failed = false;
     $rule->validate('slug', $value, function () use (&$failed): void { $failed = true; });
-    self::assertTrue($failed, 'Expected slug "' . $value . '" to fail but it passed.');
+    self::assertTrue($failed, 'Expected slug "' . var_export($value, true) . '" to fail but it passed.');
 }
tests/Unit/Http/AlbumSlugCrudTest.php (1)

98-378: Extract a reusable payload builder for album/tag-album patch requests.

The repeated full payload arrays across many tests increase maintenance drift risk when request fields change. A small helper (base payload + overrides) would keep these tests easier to update and less error-prone.


ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 90b0c8b and 4e6c42d.

📒 Files selected for processing (49)
  • .github/copilot-instructions.md
  • app/Contracts/Http/Requests/RequestAttribute.php
  • app/Http/Kernel.php
  • app/Http/Middleware/ResolveAlbumSlug.php
  • app/Http/Requests/Album/UpdateAlbumRequest.php
  • app/Http/Requests/Album/UpdateTagAlbumRequest.php
  • app/Http/Resources/Editable/EditableBaseAlbumResource.php
  • app/Http/Resources/Models/HeadAlbumResource.php
  • app/Http/Resources/Models/HeadTagAlbumResource.php
  • app/Models/BaseAlbumImpl.php
  • app/Models/Extensions/BaseAlbum.php
  • app/Rules/SlugRule.php
  • database/migrations/2026_02_27_000001_add_slug_to_base_albums.php
  • docs/specs/4-architecture/features/019-friendly-urls/plan.md
  • docs/specs/4-architecture/features/019-friendly-urls/spec.md
  • docs/specs/4-architecture/features/019-friendly-urls/tasks.md
  • docs/specs/4-architecture/open-questions.md
  • docs/specs/4-architecture/roadmap.md
  • lang/ar/gallery.php
  • lang/bg/gallery.php
  • lang/cz/gallery.php
  • lang/de/gallery.php
  • lang/el/gallery.php
  • lang/en/gallery.php
  • lang/es/gallery.php
  • lang/fa/gallery.php
  • lang/fr/gallery.php
  • lang/hu/gallery.php
  • lang/it/gallery.php
  • lang/ja/gallery.php
  • lang/nl/gallery.php
  • lang/no/gallery.php
  • lang/pl/gallery.php
  • lang/pt/gallery.php
  • lang/ru/gallery.php
  • lang/sk/gallery.php
  • lang/sv/gallery.php
  • lang/vi/gallery.php
  • lang/zh_CN/gallery.php
  • lang/zh_TW/gallery.php
  • resources/js/components/forms/album/AlbumProperties.vue
  • resources/js/lychee.d.ts
  • resources/js/services/album-service.ts
  • routes/web_v2.php
  • tests/Unit/Http/AlbumSlugCrudTest.php
  • tests/Unit/Http/Requests/Album/UpdateAlbumRequestTest.php
  • tests/Unit/Http/Requests/Album/UpdateTagAlbumRequestTest.php
  • tests/Unit/Middleware/ResolveAlbumSlugTest.php
  • tests/Unit/Rules/SlugRuleTest.php

@codecov
Copy link

codecov bot commented Feb 27, 2026

Codecov Report

❌ Patch coverage is 93.90244% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.55%. Comparing base (90b0c8b) to head (1542b95).
⚠️ Report is 1 commits behind head on master.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ildyria ildyria merged commit f2597fb into master Feb 28, 2026
44 checks passed
@ildyria ildyria deleted the slug-as-album-id branch February 28, 2026 07:16
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.

[Enhancement] Pretty/custom URLs for public albums Friendly URLs instead of IDs

1 participant