Skip to content

Add collection management features including CRUD operations, slug va… - #16

Merged
ebulgakov merged 3 commits into
mainfrom
feature/collections-imlementation
Jul 30, 2026
Merged

Add collection management features including CRUD operations, slug va…#16
ebulgakov merged 3 commits into
mainfrom
feature/collections-imlementation

Conversation

@ebulgakov

@ebulgakov ebulgakov commented Jul 30, 2026

Copy link
Copy Markdown
Owner

…lidation, and internationalization support

Summary by CodeRabbit

  • New Features

    • Added authenticated collections pages for viewing, creating, editing, and listing collections.
    • Added collection forms with name, description, sharing, slug validation, availability checks, and confirmation warnings.
    • Added collection creation, editing, detail, listing, and slug-availability functionality.
    • Added localized English and Russian text for collection workflows.
  • Bug Fixes

    • Improved validation and field-specific error handling for invalid or duplicate collection data.
    • Prevented malformed collection identifiers and stale slug-check results from causing errors.

@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
linkfolio Ready Ready Preview Jul 30, 2026 4:43pm

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ebulgakov, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 9 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 81351e8e-dd24-4d17-8105-9cbcf5e12194

📥 Commits

Reviewing files that changed from the base of the PR and between aaa7ab3 and df046bc.

📒 Files selected for processing (8)
  • app/features/collection-form/model/__tests__/use-collection-form.test.ts
  • app/features/collection-form/model/use-collection-form.ts
  • app/features/collection-form/ui/collection-form.vue
  • app/pages/collections/[id]/edit.vue
  • app/pages/collections/index.vue
  • server/api/collections/[id].patch.ts
  • server/api/collections/index.post.ts
  • server/utils/session.ts
📝 Walkthrough

Walkthrough

Adds authenticated collection APIs, create/edit form workflows, collection pages, slug validation and availability checks, uniqueness error handling, localized English/Russian UI text, and comprehensive composable tests.

Changes

Collections backend

Layer / File(s) Summary
Server contracts and collection endpoints
server/utils/*, server/api/collections/*, server/db/schema.ts, package.json
Adds authenticated collection CRUD and slug-availability endpoints with Zod validation, ownership checks, UUID validation, structured validation errors, and unique-conflict mapping.
Client API contracts
app/shared/api/*, app/shared/lib/*, app/shared/testing/*
Adds collection models, API wrappers, field-error extraction, slug validation, public exports, and Vitest API mocks.

Collection form and pages

Layer / File(s) Summary
Collection form workflow
app/features/collection-form/*
Adds create/edit form state, slug synchronization and availability checks, submission handling, shared-slug confirmation, validation UI, and tests for the composable behavior.
Authenticated pages and localization
app/middleware/auth.ts, app/pages/collections/*, app/pages/new-collection.vue, i18n/locales/*
Adds auth middleware, collection list/create/edit/detail pages, and English/Russian translations for collection screens and validation messages.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CollectionForm
  participant CollectionAPI
  participant Session
  participant Database
  User->>CollectionForm: enter collection fields
  CollectionForm->>CollectionAPI: check slug availability
  CollectionAPI->>Session: resolve authenticated user
  Session-->>CollectionAPI: return user id
  CollectionAPI->>Database: query slug conflict
  Database-->>CollectionAPI: return availability
  CollectionForm->>CollectionAPI: submit collection
  CollectionAPI->>Database: insert or update owned collection
  Database-->>CollectionAPI: return collection or validation error
  CollectionAPI-->>CollectionForm: return submission result
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change set: collection management with CRUD and slug validation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/collections-imlementation

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.

…ssertions

Wraps composable calls in a local effectScope() to silence a Vue
onScopeDispose warning from testing outside a component context, and
tightens submit-mapping assertions to check the actual payload sent
(including description's "" -> null boundary mapping) rather than
just call counts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ebulgakov
ebulgakov marked this pull request as ready for review July 30, 2026 15:52

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (3)
app/features/collection-form/model/__tests__/use-collection-form.test.ts (2)

151-228: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider covering the availability-check rejection path.

scheduleSlugCheck's .catch resets slugStatus to "idle" (re-enabling submit) and is currently untested; a regression there silently changes submit gating. Same for onScopeDispose clearing a pending debounce timer on scope stop.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/features/collection-form/model/__tests__/use-collection-form.test.ts`
around lines 151 - 228, The debounced slug availability tests cover successful
and stale responses but not rejection or scope cleanup. Extend the
“useCollectionForm - debounced slug availability check” suite to verify a
rejected check resets slugStatus to “idle” and to verify disposing the effect
scope clears a pending debounce so checkSlugAvailabilityMock is not called.

51-51: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid duplicating the i18n output in the test helper

GENERIC_ERROR mirrors tMock’s errors.generic value, so if ~/shared/testing/mocks/i18n changes how it resolves keys the test expectation drifts. Either derive the helper from the mock, import it from the mock utilities, or assert that tMock was called with "errors.generic" when handling the mapped error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/features/collection-form/model/__tests__/use-collection-form.test.ts` at
line 51, The test helper’s GENERIC_ERROR duplicates the i18n mock output and can
drift from tMock. Update the error-handling assertions in the collection form
test to derive the expected value from tMock or assert the "errors.generic"
translation key, reusing the existing i18n mock behavior instead of hardcoding
the message.
app/features/collection-form/model/use-collection-form.ts (1)

149-156: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Trim text fields in toPayload.

name is sent verbatim while the server's uniqueness index compares lower(trim(name)) (server/utils/collection-errors.ts), so " My List " persists with padding yet collides with "My List". Whitespace-only descriptions likewise survive the || null check.

♻️ Proposed fix
 function toPayload(): CollectionInput {
+  const description = form.description.trim();
   return {
-    name: form.name,
-    description: form.description || null,
+    name: form.name.trim(),
+    description: description || null,
     shared: form.shared,
     slug: form.slug
   };
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/features/collection-form/model/use-collection-form.ts` around lines 149 -
156, Update toPayload to trim the name and description fields before submission.
Ensure whitespace-only descriptions become null after trimming, while preserving
the existing payload fields and behavior for non-text values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/features/collection-form/model/use-collection-form.ts`:
- Around line 18-28: Update slugify to limit the generated slug to the
validator’s 64-character maximum, trimming any trailing hyphen after truncation.
Keep the existing lowercase, normalization, and edge-hyphen removal behavior,
and ensure isValidSlugFormat continues validating the resulting slug.
- Around line 158-193: Add an early return at the start of performSubmit when
pending.value is already true, before resetting form state or issuing
createCollection/updateCollection calls. Preserve the existing submission flow
for non-pending requests and ensure concurrent confirmAndSubmit invocations
cannot duplicate the request.

In `@app/features/collection-form/ui/collection-form.vue`:
- Line 25: Type the formRef declaration used by onSubmit so its value exposes
the VForm validate method instead of remaining unknown. Update the
useTemplateRef call with the VForm instance type, or use a nullable ref<VForm>
type, while preserving the existing template reference and validation flow.

In `@app/pages/collections/`[id]/edit.vue:
- Line 20: Update the error alert condition in the collection edit view to
distinguish a 404 response from other GET failures: display
collections.errors.notFound only when the error status is 404, and display
collections.errors.loadFailed for 5xx or network failures while preserving the
existing error visibility behavior.

In `@app/pages/collections/`[id]/index.vue:
- Around line 1-11: Update the collection detail page around the setup block and
template to read the route parameter id and fetch the corresponding collection
through the authenticated /api/collections/:id endpoint. Add loading, not-found,
and error states, and render the loaded collection’s fields instead of the
generic pages.collectionDetail.title heading while preserving the auth
middleware.

In `@app/pages/collections/index.vue`:
- Around line 29-46: The routed v-list-item and its append NuxtLink create
nested interactive links. Update the collection list markup around the v-for so
the edit action is outside the :to-enabled v-list-item, or make the row
non-routed while preserving navigation and the existing edit destination.
- Around line 9-13: Make both authenticated collection async-data keys
user-scoped: update useAsyncData in app/pages/collections/index.vue around lines
9-13 to include a stable session/user discriminator in "collections", and update
useAsyncData in app/pages/collections/[id]/edit.vue around lines 12-15 to
include the same discriminator in `collection-${id}`. Keep the request behavior
unchanged while ensuring logout/login or account switching cannot reuse another
user’s cached data.

In `@server/api/collections/index.post.ts`:
- Around line 18-26: Authenticate before request parsing in both
server/api/collections/index.post.ts lines 18-26 and
server/api/collections/[id].patch.ts lines 24-32: move requireUserId(event)
ahead of readBody and collectionSchema.safeParse, while preserving the existing
validation and mutation behavior for authenticated callers.

In `@server/utils/session.ts`:
- Around line 23-37: Update requireUserId to use a trusted fixed or
configuration-derived auth origin instead of url.origin from
getRequestURL(event), while preserving the forwarded cookie behavior. Configure
the $fetch call with a timeout and appropriate abort handling so an unavailable
auth endpoint cannot hang the request, while retaining the existing 401 response
for missing sessions.

---

Nitpick comments:
In `@app/features/collection-form/model/__tests__/use-collection-form.test.ts`:
- Around line 151-228: The debounced slug availability tests cover successful
and stale responses but not rejection or scope cleanup. Extend the
“useCollectionForm - debounced slug availability check” suite to verify a
rejected check resets slugStatus to “idle” and to verify disposing the effect
scope clears a pending debounce so checkSlugAvailabilityMock is not called.
- Line 51: The test helper’s GENERIC_ERROR duplicates the i18n mock output and
can drift from tMock. Update the error-handling assertions in the collection
form test to derive the expected value from tMock or assert the "errors.generic"
translation key, reusing the existing i18n mock behavior instead of hardcoding
the message.

In `@app/features/collection-form/model/use-collection-form.ts`:
- Around line 149-156: Update toPayload to trim the name and description fields
before submission. Ensure whitespace-only descriptions become null after
trimming, while preserving the existing payload fields and behavior for non-text
values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d3167cae-df08-4050-b9ff-dbca9eee0761

📥 Commits

Reviewing files that changed from the base of the PR and between 6764714 and aaa7ab3.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (26)
  • app/features/collection-form/index.ts
  • app/features/collection-form/model/__tests__/use-collection-form.test.ts
  • app/features/collection-form/model/use-collection-form.ts
  • app/features/collection-form/ui/collection-form.vue
  • app/middleware/auth.ts
  • app/pages/collections/[id]/edit.vue
  • app/pages/collections/[id]/index.vue
  • app/pages/collections/index.vue
  • app/pages/new-collection.vue
  • app/shared/api/collections.ts
  • app/shared/api/index.ts
  • app/shared/lib/index.ts
  • app/shared/lib/validators.ts
  • app/shared/testing/mocks/collections-api.ts
  • i18n/locales/en.json
  • i18n/locales/ru.json
  • package.json
  • server/api/collections/[id].get.ts
  • server/api/collections/[id].patch.ts
  • server/api/collections/check-slug.get.ts
  • server/api/collections/index.get.ts
  • server/api/collections/index.post.ts
  • server/db/schema.ts
  • server/utils/collection-errors.ts
  • server/utils/session.ts
  • server/utils/validation.ts

Comment thread app/features/collection-form/model/use-collection-form.ts
Comment thread app/features/collection-form/model/use-collection-form.ts
Comment thread app/features/collection-form/ui/collection-form.vue Outdated
Comment thread app/pages/collections/[id]/edit.vue Outdated
Comment on lines +1 to +11
<script lang="ts" setup>
definePageMeta({ middleware: "auth" });

const { t } = useI18n();
</script>

<template>
<v-container class="fill-height d-flex align-center justify-center">
<h1>{{ t("pages.collectionDetail.title") }}</h1>
</v-container>
</template>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Implement the collection detail data flow.

This page never reads route.params.id or calls /api/collections/:id, so every authenticated ID renders the same generic heading. Load the collection through the authenticated API and render its fields with loading, 404, and error states.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/pages/collections/`[id]/index.vue around lines 1 - 11, Update the
collection detail page around the setup block and template to read the route
parameter id and fetch the corresponding collection through the authenticated
/api/collections/:id endpoint. Add loading, not-found, and error states, and
render the loaded collection’s fields instead of the generic
pages.collectionDetail.title heading while preserving the auth middleware.

Comment thread app/pages/collections/index.vue Outdated
Comment thread app/pages/collections/index.vue Outdated
Comment thread server/api/collections/index.post.ts Outdated
Comment thread server/utils/session.ts
Fixes the security-relevant items (spoofable Host header driving
requireUserId's self-fetch origin, auth check running after body
validation on mutating routes, non-user-scoped useAsyncData cache
keys, nested interactive links in the collections list) plus the
smaller correctness issues (double-submit guard, slug auto-sync
overflowing the 64-char limit, untrimmed name/description, 404 vs.
generic load-failure messaging, unresolved formRef typing) and the
two test nitpicks (slug-check rejection/scope-dispose coverage,
GENERIC_ERROR derived from the i18n mock instead of duplicated).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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