Skip to content

Feature/poly 16 listing tags backend#35

Merged
SamanSP1386 merged 4 commits intodevfrom
feature/POLY-16-Listing-Tags-Backend
Feb 24, 2026
Merged

Feature/poly 16 listing tags backend#35
SamanSP1386 merged 4 commits intodevfrom
feature/POLY-16-Listing-Tags-Backend

Conversation

@mattphanm
Copy link
Copy Markdown
Collaborator

@mattphanm mattphanm commented Feb 8, 2026

Linked Issues

Closes #16
Linear: POLY-16 (e.g., POLY-123)

Summary

  • Added tags: v.optional(v.array(v.string())) to searchAndFilteringListing()
  • Added query and filtered by tag > allows users to filter all listings by desired tag

How to Test

Steps to verify locally:

  • npm run lint
  • npm run typecheck
  • npm test
  • Manual flow:
    1. npm run dev:backend (in terminal A)
    2. npm run dev (in terminal B)
    3. Verify the change: <describe expected behavior/screens>

Checklist

  • Tests added/updated (if applicable)
  • Lint/tests pass locally (npm run lint)
  • Docs updated (README/ADR/changelog if needed)
  • Follows conventional commit format
  • No merge conflicts with dev

Screenshots / Demos

(if UI or visible behavior - attach images, videos, or GIFs)

Summary by CodeRabbit

  • New Features
    • Filter listings by tags—search results can be narrowed by selecting one or more tags.
    • Add tags when creating or updating a listing; tags are validated and normalized automatically (trimmed, deduplicated, length-checked, and capped).
    • Tag filtering is applied only when tags are provided, preserving prior behavior otherwise.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 8, 2026

📝 Walkthrough

Walkthrough

Added tag-based filtering and tag handling to listings: search now accepts optional tags and applies tag intersection filtering; createListing accepts and normalizes optional tags; tag normalization enforces min/max lengths, trimming, deduplication, and a maximum tag count.

Changes

Cohort / File(s) Summary
Listings: tag handling
backend/convex/listings.ts
Added optional tags to searchAndFilterListings and createListing signatures. Implemented tag normalization/validation (trim, dedupe, enforce MIN_TAG_LENGTH, MAX_TAG_LENGTH, limit to MAX_TAGS) and applied tag-based intersection filtering when tags provided. Minor formatting alignment in create flow.
Manifest
.../manifest
Small manifest metadata/content adjustments. Lines changed: +8/-2.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant API as Backend API
    participant DB as Database

    Client->>API: searchAndFilterListings(filters, tags?)
    API->>API: normalize/validate tags (trim, dedupe, length, limit)
    API->>DB: query listings by filters (price/other)
    DB-->>API: listings[]
    API->>API: if tags present -> filter listings by tag intersection
    API-->>Client: filtered listings[]

    Client->>API: createListing(data, tags?)
    API->>API: normalize/validate tags (trim, dedupe, length, limit)
    API->>DB: insert listing with normalized tags
    DB-->>API: created listing
    API-->>Client: created listing
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • jaydonkc
  • evan-taylor

Poem

"🐰 I hopped through code with ears held high,
Tags trimmed and sorted, neat and spry,
Min, max, and five—no duplicates stay,
Listings now bloom in a tidy array—
A carrot-coded cheer to brighten the sky!"

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The PR implements tag-based filtering for listings (#16), but the linked issue titled 'feat: add linkedin' appears unrelated to the actual tag filtering implementation. Verify that issue #16 actually requires tag-based filtering functionality rather than LinkedIn integration, or confirm the correct linked issue.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Feature/poly 16 listing tags backend' clearly describes the main change: adding tag-based filtering functionality to the backend listings system, directly matching the changeset.
Description check ✅ Passed The description includes linked issue, summary of changes, testing steps, and a checklist following the template, though all checklist items remain unchecked.
Out of Scope Changes check ✅ Passed All changes focus on tag-based filtering for listings search, which aligns with the stated objective of adding tags support to backend listing operations.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/POLY-16-Listing-Tags-Backend

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.

Copy link
Copy Markdown
Contributor

@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.

Caution

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

⚠️ Outside diff range comments (1)
backend/convex/listings.ts (1)

75-84: 🛠️ Refactor suggestion | 🟠 Major

normalizeTags uses hardcoded magic numbers instead of TAG_CONSTRAINTS.

Lines 81 and 83 hardcode 1, 20, and 5 rather than referencing TAG_CONSTRAINTS.MIN_TAG_LENGTH, TAG_CONSTRAINTS.MAX_TAG_LENGTH, and TAG_CONSTRAINTS.MAX_TAGS defined at lines 13–17. If the constants are ever updated, this function will silently diverge.

Also, minor typo on line 75: "tages" → "tags".

Proposed fix
-// Normalize tages to lowercase and within 1-20 characters exclusive
+// Normalize tags to lowercase, deduplicate, and enforce length constraints
 function normalizeTags(tags: string[]): string[] {
   return [
     ...new Set(
       tags
         .map((tag) => tag.trim().toLowerCase())
-        .filter((tag) => tag.length >= 1 && tag.length <= 20)
+        .filter((tag) => tag.length >= TAG_CONSTRAINTS.MIN_TAG_LENGTH && tag.length <= TAG_CONSTRAINTS.MAX_TAG_LENGTH)
     ),
-  ].slice(0, 5);
+  ].slice(0, TAG_CONSTRAINTS.MAX_TAGS);
 }
🧹 Nitpick comments (2)
backend/convex/listings.ts (2)

232-245: Tag validation logic is duplicated between createListing and updateListing.

Lines 232–245 here and lines 283–297 in updateListing are identical. Extract a shared validateTags(tags: string[]) helper (similar to validateTitle / validateImages) to keep this DRY.

Proposed refactor
+function validateTags(tags: string[]) {
+  if (tags.length > TAG_CONSTRAINTS.MAX_TAGS) {
+    throw new Error(`Maximum ${TAG_CONSTRAINTS.MAX_TAGS} tags allowed`);
+  }
+  for (const tag of tags) {
+    const trimmed = tag.trim();
+    if (!trimmed) {
+      throw new Error('Empty tags are not allowed');
+    }
+    if (trimmed.length > TAG_CONSTRAINTS.MAX_TAG_LENGTH) {
+      throw new Error(`Tags must be ${TAG_CONSTRAINTS.MAX_TAG_LENGTH} characters or less`);
+    }
+  }
+}

Then in both createListing and updateListing:

-    if (args.tags) {
-      if (args.tags.length > TAG_CONSTRAINTS.MAX_TAGS) {
-        throw new Error(`Maximum ${TAG_CONSTRAINTS.MAX_TAGS} tags allowed`);
-      }
-      for (const tag of args.tags) {
-        const trimmed = tag.trim();
-        if (!trimmed) {
-          throw new Error('Empty tags are not allowed');
-        }
-        if (trimmed.length > TAG_CONSTRAINTS.MAX_TAG_LENGTH) {
-          throw new Error(`Tags must be ${TAG_CONSTRAINTS.MAX_TAG_LENGTH} characters or less`);
-        }
-      }
-    }
+    if (args.tags) {
+      validateTags(args.tags);
+    }

298-332: Nit: normalizedTags is computed unconditionally but only used when args.tags !== undefined.

Consider moving the normalization inside the conditional block at line 330 to avoid the unnecessary call when tags aren't provided.

Copy link
Copy Markdown
Collaborator

@SamanSP1386 SamanSP1386 left a comment

Choose a reason for hiding this comment

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

There is some few warnings, check the ai bot and try to fix them.

Copy link
Copy Markdown
Contributor

@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.

Caution

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

⚠️ Outside diff range comments (1)
backend/convex/listings.ts (1)

163-258: ⚠️ Potential issue | 🟠 Major

Apply tag filtering in searchAndFilterListings.

filters.tags is accepted but never used, so tag filters are ignored and results can be broader than requested. This breaks the expected behavior and the PR’s objective.

✅ Suggested fix (normalize tags + filter in both paths)
   const filters = args.filters ?? {};
   const searchTerm = filters.searchTerm?.trim();
   const sortBy = filters.sortBy ?? 'newest';
+  const normalizedTags = filters.tags ? normalizeSearchTags(filters.tags) : [];
+  const hasTags = normalizedTags.length > 0;

@@
       // Filter out hidden content
       results = results.filter((l) => l.isHidden !== true);
+
+      // Apply tag filter (any-match)
+      if (hasTags) {
+        results = results.filter((l) =>
+          (l.tags ?? []).some((tag) => normalizedTags.includes(tag))
+        );
+      }

@@
-    const needsPostFiltering =
+    const needsPostFiltering =
+      hasTags ||
       // maxPrice needs post-filtering for non-price-sorted queries (can use db-level filter for price-sorted)
       (filters.maxPrice !== undefined && sortBy !== 'price_asc' && sortBy !== 'price_desc') ||
       (filters.minPrice !== undefined && sortBy !== 'price_asc' && sortBy !== 'price_desc') ||
       // Condition filter needs post-filtering when using price-based indexes (which don't include condition)
       (filters.condition !== undefined && (sortBy === 'price_asc' || sortBy === 'price_desc'));

@@
     const filtered = allResults.filter((l) => {
+      if (hasTags && !(l.tags ?? []).some((tag) => normalizedTags.includes(tag))) return false;
       if (filters.maxPrice !== undefined && l.price > filters.maxPrice) return false;
       if (
         sortBy !== 'price_asc' &&
         sortBy !== 'price_desc' &&
         filters.minPrice !== undefined &&
         l.price < filters.minPrice
       )
         return false;
       if (filters.condition && l.condition !== filters.condition) return false;
       return true;
     });

Also applies to: 260-382

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/convex/listings.ts` around lines 163 - 258, searchAndFilterListings
accepts filters.tags but never applies it; normalize requested tags (e.g., const
normalizedTags = (filters.tags ?? []).map(t =>
t.trim().toLowerCase()).filter(Boolean)) and then filter listings to only
include items whose listing.tags (normalize each listing tag to lowercase/trim)
contain every requested tag. In the search branch (inside the if (searchTerm)
block) add this filter after price filtering (before hidden filtering/sorting)
using results = results.filter(l => normalizedTags.every(t => (l.tags ??
[]).map(x=>x.trim().toLowerCase()).includes(t))). In the non-search code path
(the other query/paginate branch) apply the same normalization and either add an
equivalent DB-level constraint if supported or perform the same post-query
in-memory filter before pagination so both branches respect filters.tags.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@backend/convex/listings.ts`:
- Around line 163-258: searchAndFilterListings accepts filters.tags but never
applies it; normalize requested tags (e.g., const normalizedTags = (filters.tags
?? []).map(t => t.trim().toLowerCase()).filter(Boolean)) and then filter
listings to only include items whose listing.tags (normalize each listing tag to
lowercase/trim) contain every requested tag. In the search branch (inside the if
(searchTerm) block) add this filter after price filtering (before hidden
filtering/sorting) using results = results.filter(l => normalizedTags.every(t =>
(l.tags ?? []).map(x=>x.trim().toLowerCase()).includes(t))). In the non-search
code path (the other query/paginate branch) apply the same normalization and
either add an equivalent DB-level constraint if supported or perform the same
post-query in-memory filter before pagination so both branches respect
filters.tags.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 30eb028 and 610217e.

📒 Files selected for processing (1)
  • backend/convex/listings.ts

@SamanSP1386 SamanSP1386 merged commit 1bb0535 into dev Feb 24, 2026
3 checks passed
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.

2 participants