Skip to content

refactor: unify authentication handling across backend modules#60

Merged
evan-taylor merged 1 commit intodevfrom
evan/fixes
Mar 11, 2026
Merged

refactor: unify authentication handling across backend modules#60
evan-taylor merged 1 commit intodevfrom
evan/fixes

Conversation

@evan-taylor
Copy link
Copy Markdown
Collaborator

@evan-taylor evan-taylor commented Mar 11, 2026

  • Replaced direct calls to getUserIdentity with requireAuthUserId in various modules to ensure consistent user authentication checks.
  • Updated ownership and user ID retrieval logic in listings, messages, profiles, and saved listings to utilize stable user IDs.
  • Enhanced error handling for unauthorized actions across multiple mutations and queries.
  • Removed legacy participant ID handling in messages and conversations to streamline user identification.
  • Improved overall code maintainability and clarity by consolidating authentication logic.

Linked Issues

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

Summary

Briefly explain the change and why.

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

    • Listing density variants for optimized home feed display
  • Bug Fixes

    • Improved initial loading state handling to prevent premature empty screens
    • Enhanced empty state messaging based on active filters and data presence
  • Style

    • Refined login verification code input styling
    • Applied theme token styling to tag input and picker components
    • Adjusted home feed spacing and card layout
  • Documentation

    • Updated database schema migration documentation

- Replaced direct calls to `getUserIdentity` with `requireAuthUserId` in various modules to ensure consistent user authentication checks.
- Updated ownership and user ID retrieval logic in listings, messages, profiles, and saved listings to utilize stable user IDs.
- Enhanced error handling for unauthorized actions across multiple mutations and queries.
- Removed legacy participant ID handling in messages and conversations to streamline user identification.
- Improved overall code maintainability and clarity by consolidating authentication logic.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 11, 2026

📝 Walkthrough

Walkthrough

This PR refactors the authentication and user identity handling across the backend, replacing a complex alias-based participant identification system with stable user IDs through new auth helper utilities. Corresponding frontend changes remove alias/participant-key logic and introduce UI component theming and layout density options.

Changes

Cohort / File(s) Summary
Backend Authentication Infrastructure
backend/convex/lib/authIdentity.ts
Introduces new utility module with AuthCtx type, requireAuthUserId, and getStableUserId helpers for consistent authentication and error handling across the backend.
Backend Core Features (Auth Refactoring)
backend/convex/messages.ts, backend/convex/listings.ts, backend/convex/profiles.ts
Replaces direct identity.subject usage and complex alias/participant-key logic with stable userId retrieval via new auth helpers; simplifies conversation grouping and participant comparisons.
Backend Supporting Features (Auth Refactoring)
backend/convex/savedListings.ts, backend/convex/pushNotifications.ts, backend/convex/reports.ts
Replaces manual authentication flow with requireAuthUserId helper for consistent user ID retrieval and error handling in save/notification/reporting flows.
Backend Tests
backend/convex/__tests__/messages.test.ts, backend/convex/__tests__/profiles.test.ts
Removes legacy alias-based and cross-sibling conversation tests; updates test data to use stable IDs; adds profile duplicate creation error test.
Documentation
docs/SCHEMA_MIGRATION.md
Updates migration guidance to reflect removal of alias compatibility and clarifies stable user ID handling with database wipe requirements.
Frontend UI Components (Theming)
frontend/components/TagInput.tsx, frontend/components/TagPicker.tsx
Replaces hard-coded color, spacing, and border values with token-based theming system from theme/tokens module.
Frontend UI Components (Functional)
frontend/components/ListingCard.tsx
Adds density prop ('default' or 'home') to enable layout variants; introduces home-specific styling, image aspect ratio, and title/price row formatting.
Frontend Conversation UI
frontend/app/conversations/[id].tsx
Removes participant-key helper functions and replaces participant ID logic with direct currentUserId comparisons.
Frontend Pages
frontend/app/(tabs)/index.tsx, frontend/app/auth/login.tsx
Updates listings page with hasLoadedOnceRef gate for initial load, empty-state handling, and grid styling; minor login form styling updates.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #39: Refactors authentication/identity usage across backend modules (messages, listings, profiles, tests) with the same direct code-level changes to auth handling and identity resolution patterns.
  • PR #40: Modifies the same backend modules (messages.ts, listings.ts) and converts message/listing mutations to actions while refactoring auth/alias handling.
  • PR #47: Modifies messaging codepath (messages.ts and tests) and updates getOrCreateConversation and participant ID handling logic alongside message field changes.

Suggested reviewers

  • jaydonkc

Poem

🐰 Auth was tangled, aliases flew,
Now stable IDs shine bright and true!
The rabbit hops through cleaner code,
With helpers lighting up the road—
No more alias twists and turns,
Just userId dreams, the bunny learns! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description addresses the key changes but lacks required template sections: Linked Issues are incomplete (missing actual issue/Linear key), Summary is template text only, How to Test is all template boilerplate, and Checklist is entirely unchecked. Complete all template sections: fill in actual issue numbers, provide a concrete summary of the refactoring rationale, complete testing instructions with specific expected behaviors, and update the checklist to reflect work completed.
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: unifying authentication handling across backend modules, which is the primary objective reflected throughout the changeset.

✏️ 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 evan/fixes

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.

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (6)
docs/SCHEMA_MIGRATION.md (2)

134-138: ⚠️ Potential issue | 🟡 Minor

Documentation inconsistency with implementation.

Lines 136-137 state that v.string() fields hold "Auth identities (strings from identity.subject)", but the PR's implementation uses getAuthUserId/requireAuthUserId which returns Id<'users'> (Convex document IDs), not identity.subject.

Consider updating the summary to reflect the actual implementation: these fields now store stable Convex user document IDs, not raw auth identity subjects.

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

In `@docs/SCHEMA_MIGRATION.md` around lines 134 - 138, The docs incorrectly say
v.string() fields hold "Auth identities (strings from identity.subject)"; update
the summary to say these fields store stable Convex user document IDs
(Id<'users'>) as returned by getAuthUserId and requireAuthUserId in the
implementation, and change any wording that implies raw auth subject strings to
instead reference Convex user IDs / document references so the documentation
matches the actual code behavior.

45-51: ⚠️ Potential issue | 🟡 Minor

Code example contradicts the warning above.

Line 19 warns: "Do not use identity.subject for ownership keys." However, the "After" example on line 50 shows sellerId: identity.subject as the correct approach.

The example should demonstrate using requireAuthUserId:

📝 Suggested fix
 ### Validation
 
 ```typescript
 // Before (with v.id('users')):
 sellerId: identity.subject as Id<'users'>; // ❌ Unsafe cast
 
 // After (with v.string()):
-sellerId: identity.subject; // ✅ Type-safe, no cast needed
+const userId = await requireAuthUserId(ctx);
+sellerId: userId; // ✅ Type-safe stable ID
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @docs/SCHEMA_MIGRATION.md around lines 45 - 51, The "After" example
contradicts the warning by using identity.subject directly; replace that line so
the example calls requireAuthUserId(ctx) to obtain a stable, type-safe userId
and then assigns that value to sellerId instead of using identity.subject;
update the snippet to reference requireAuthUserId and sellerId (and remove the
unsafe cast example or keep it as the "Before" example) so the docs demonstrate
using requireAuthUserId for ownership keys.


</details>

</blockquote></details>
<details>
<summary>backend/convex/listings.ts (2)</summary><blockquote>

`149-170`: _⚠️ Potential issue_ | _🟠 Major_

**Same ownership comparison issue affects visibility.**

The `isOwner` check on line 156 will fail for existing listings where `sellerId` is stored as `identity.subject`. Owners viewing their own hidden listings may see them as "not found".

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @backend/convex/listings.ts around lines 149 - 170, getListing's owner check
fails when listing.sellerId is stored as an identity object (e.g. { subject })
so isOwner (computed in the getListing handler using getStableUserId) can be
false for actual owners; fix by normalizing the seller id before comparison (in
the getListing handler) — derive a canonicalSellerId from listing.sellerId
(extract .subject if it's an object, otherwise use the string) and then set
isOwner = !!userId && userId === canonicalSellerId so hidden and non-active
visibility checks behave correctly.


</details>

---

`30-43`: _⚠️ Potential issue_ | _🔴 Critical_

**Breaking change: ownership verification will fail for existing listings.**

The `verifyOwnership` function now compares `listing.sellerId` (stored as `identity.subject` for existing records) against `userId` (now `Id<'users'>`). These formats are incompatible, causing ownership checks to fail for listings created before this change.

**Impact:** Users will receive "You are not the owner of this listing" errors when trying to update or delete their existing listings.

**Recommendation:** Either:
1. Migrate existing `listings.sellerId` values to the new `Id<'users'>` format, OR
2. Add a fallback lookup that maps `identity.subject` → `users._id` during the transition period

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @backend/convex/listings.ts around lines 30 - 43, verifyOwnership currently
compares listing.sellerId to the Id<'users'> returned by requireAuthUserId,
which breaks for legacy listings where sellerId stores identity.subject; update
verifyOwnership to handle both formats by adding a fallback: after fetching
listing and userId (via requireAuthUserId), first compare listing.sellerId ===
userId, and if that fails and listing.sellerId appears to be a legacy
identity.subject value, resolve the corresponding user record (query users for a
document whose identity.subject matches listing.sellerId) and compare its _id to
userId; if a match return the listing, otherwise throw the existing ConvexError.
Ensure you reference verifyOwnership, listing.sellerId, identity.subject,
requireAuthUserId and users._id when implementing the lookup.


</details>

</blockquote></details>
<details>
<summary>backend/convex/profiles.ts (1)</summary><blockquote>

`88-100`: _⚠️ Potential issue_ | _🔴 Critical_

**Breaking change: existing profiles will not be found.**

The `getCurrentProfile` query now uses `Id<'users'>` to query the `by_userId` index. Existing profiles stored with `userId = identity.subject` will not be matched, causing authenticated users to appear as if they have no profile.

**Impact:** Users will be prompted to re-onboard despite having existing profiles.

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @backend/convex/profiles.ts around lines 88 - 100, getCurrentProfile is now
querying the profiles.by_userId index using an Id<'users'> typed userId which
won't match existing rows where profiles.userId was stored as the legacy
identity.subject string; change getCurrentProfile (and/or the lookup logic) to
first attempt the lookup with the typed stable userId returned by
getStableUserId and, if that returns no result, perform a fallback lookup using
the legacy string form (String(userId) or the original identity.subject) so both
existing profiles and new typed IDs are matched when querying the 'by_userId'
index.


</details>

</blockquote></details>
<details>
<summary>backend/convex/messages.ts (1)</summary><blockquote>

`510-556`: _⚠️ Potential issue_ | _🟠 Major_

**Existing conversation lookup will fail, creating duplicates.**

In `getOrCreateConversation`, the query on line 533-538 searches `by_listing_buyer_seller` with the new `buyerId` format. For users who previously started conversations (stored with `identity.subject`), the existing conversation won't be found, and a duplicate will be created.

</blockquote></details>

</blockquote></details>
🧹 Nitpick comments (4)
frontend/components/ListingCard.tsx (1)

296-307: Empty style objects can be removed.

listingTitleHome and listingPriceHome are empty objects with only comments. If no actual style overrides are needed for home density, consider removing these to reduce noise:

♻️ Suggested cleanup
   listingTitle: {
     ...typography.body,
     color: colors.primary,
     flex: 1,
     minWidth: 0,
   },
-  listingTitleHome: {
-    // Tighter for home density
-  },
   listingPrice: {
     ...typography.title2,
     fontSize: 17,
     color: colors.accent,
     flexShrink: 0,
   },
-  listingPriceHome: {
-    // Same as base
-  },

Then update the JSX to conditionally apply only when styles exist:

-  <Text style={[styles.listingTitle, isHomeDensity && styles.listingTitleHome]} ...>
+  <Text style={styles.listingTitle} ...>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/components/ListingCard.tsx` around lines 296 - 307, Remove the no-op
style objects listingTitleHome and listingPriceHome from the styles object in
ListingCard.tsx and clean up any references in the JSX so you only apply
existing styles (e.g., listingTitle, listingPrice) — where conditional
application was used, change it to only include the style if it exists (or
remove the conditional spread) so JSX doesn't reference the removed keys; update
any className/style arrays to not include listingTitleHome/listingPriceHome.
frontend/app/(tabs)/index.tsx (1)

342-369: Redundant condition in ListEmptyComponent.

The listings.length === 0 check on line 343 is redundant since ListEmptyComponent is only rendered by FlatList when the data array is empty.

♻️ Suggested simplification
             ListEmptyComponent={
-              listings.length === 0 ? (
                 <View style={styles.stateContainer}>
                   <ScreenState
                     variant={loadError ? 'error' : 'empty'}
                     title={
                       loadError
                         ? "Couldn't load listings"
                         : hasActiveFilters
                           ? 'No listings match your filters'
                           : 'No listings yet'
                     }
                     message={
                       loadError
                         ? 'Check your connection and try again.'
                         : hasActiveFilters
                           ? 'Try a wider price range or fewer tags.'
                           : 'Be the first to post something for campus.'
                     }
                     actionLabel={
                       loadError ? undefined : hasActiveFilters ? 'Clear Filters' : undefined
                     }
                     onRetry={loadError ? refreshListings : undefined}
                     onAction={hasActiveFilters ? handleClearAll : undefined}
                   />
                 </View>
-              ) : null
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`(tabs)/index.tsx around lines 342 - 369, The ListEmptyComponent
currently wraps the ScreenState JSX with an unnecessary conditional check on
listings.length === 0; remove that redundant condition and return the
ScreenState wrapper directly from ListEmptyComponent so FlatList can control
when it's shown. Specifically, update the ListEmptyComponent prop to render the
<View style={styles.stateContainer}><ScreenState ... /></View> unconditionally
(keeping all props: variant, title, message, actionLabel, onRetry ->
refreshListings, onAction -> handleClearAll) and remove the outer ternary that
checks listings.length; no other logic changes to ScreenState, handleClearAll,
or refreshListings are needed.
backend/convex/reports.ts (1)

25-100: Inconsistent step numbering in comments.

The validation step comments are numbered 2, 5, 6, 6, 7, 8 (lines 25, 30, 55, 68, 80, 90). Steps 1, 3, 4 are missing, and step 6 appears twice. Consider renumbering for clarity or removing the numbers entirely.

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

In `@backend/convex/reports.ts` around lines 25 - 100, The inline comments in
reports.ts around the report creation flow (e.g., the comment blocks referencing
validation, target existence, duplicate check, rate limiting, create report, and
auto-hide logic that surround symbols like MAX_NOTES_LENGTH, ctx.db.get,
existingReport, recentReports, reportId, and allReports) use inconsistent
numbering (2,5,6,6,7,8); remove the numeric prefixes or renumber them
sequentially (1..N) so the steps read clearly (e.g., "1. Validate notes length",
"2. Validate target exists", "3. Check duplicate report", etc.), and update or
remove any duplicate "6." comment to keep the sequence consistent.
backend/convex/messages.ts (1)

30-36: Consider removing wrapper function.

requireStableUserId is a thin wrapper around requireAuthUserId with no additional logic. Consider using requireAuthUserId directly throughout the file to reduce indirection, or document why the wrapper exists (e.g., future expansion plans).

♻️ Proposed simplification
-/** Returns the stable auth user ID. Throws if not authenticated. */
-async function requireStableUserId(
-  ctx: MutationCtx | QueryCtx,
-  message = 'Unauthorized'
-): Promise<Id<'users'>> {
-  return await requireAuthUserId(ctx, message);
-}

Then replace all requireStableUserId(ctx) calls with requireAuthUserId(ctx) or requireAuthUserId(ctx, 'Unauthorized').

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

In `@backend/convex/messages.ts` around lines 30 - 36, The wrapper function
requireStableUserId simply forwards to requireAuthUserId without adding
behavior; remove requireStableUserId and replace its usages with direct calls to
requireAuthUserId (e.g., replace requireStableUserId(ctx) with
requireAuthUserId(ctx) or requireAuthUserId(ctx, 'Unauthorized') as needed), or
if you prefer keeping it, add a comment documenting its intended future
purpose—update any imports/exports and tests referencing requireStableUserId
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/convex/messages.ts`:
- Around line 107-114: The permission check fails because convo.buyerId /
convo.sellerId may be stored as an identity object (e.g., { subject }) while
userId is a string; update the checks to normalize both participant IDs before
comparing: derive normalizedBuyerId and normalizedSellerId (e.g., const
normalizedBuyerId = typeof convo.buyerId === 'string' ? convo.buyerId :
convo.buyerId?.subject) and use those in the isBuyer/isSeller checks and when
computing recipientId and senderId (replace references to
convo.buyerId/convo.sellerId with the normalized variables) so existing
conversations with identity objects pass the permission check.
- Around line 212-225: The listUserConversations query now uses
requireStableUserId which returns Id<'users'>, but existing conversations were
stored with buyerId/sellerId = identity.subject (legacy string), so queries
against the by_buyer and by_seller indexes miss existing rows; update
listUserConversations to query both id formats (the new stableId from
requireStableUserId and the legacy subject from ctx.auth.identity.subject) for
buyerId and sellerId (e.g., run queries for both values or use an
OR/IN-equivalent and merge/deduplicate results), referencing the
functions/values requireStableUserId, listUserConversations, by_buyer,
by_seller, buyerId, sellerId, and identity.subject so existing conversations are
returned.

In `@backend/convex/profiles.ts`:
- Around line 139-145: The duplicate-profile check only queries by userId and
misses profiles stored under identity.subject, so update the check to look for
either identifier: query profiles by userId (existingProfile) AND also query
profiles by subject (e.g., identity.subject) and throw if either query returns a
record; if there isn't already an index for subject, add or use an index such as
'by_subject' and call ctx.db.query('profiles').withIndex('by_subject', q =>
q.eq('subject', identity.subject)) (or run both queries in the createProfile
function) to ensure users cannot create a second profile.

---

Outside diff comments:
In `@backend/convex/listings.ts`:
- Around line 149-170: getListing's owner check fails when listing.sellerId is
stored as an identity object (e.g. { subject }) so isOwner (computed in the
getListing handler using getStableUserId) can be false for actual owners; fix by
normalizing the seller id before comparison (in the getListing handler) — derive
a canonicalSellerId from listing.sellerId (extract .subject if it's an object,
otherwise use the string) and then set isOwner = !!userId && userId ===
canonicalSellerId so hidden and non-active visibility checks behave correctly.
- Around line 30-43: verifyOwnership currently compares listing.sellerId to the
Id<'users'> returned by requireAuthUserId, which breaks for legacy listings
where sellerId stores identity.subject; update verifyOwnership to handle both
formats by adding a fallback: after fetching listing and userId (via
requireAuthUserId), first compare listing.sellerId === userId, and if that fails
and listing.sellerId appears to be a legacy identity.subject value, resolve the
corresponding user record (query users for a document whose identity.subject
matches listing.sellerId) and compare its _id to userId; if a match return the
listing, otherwise throw the existing ConvexError. Ensure you reference
verifyOwnership, listing.sellerId, identity.subject, requireAuthUserId and
users._id when implementing the lookup.

In `@backend/convex/profiles.ts`:
- Around line 88-100: getCurrentProfile is now querying the profiles.by_userId
index using an Id<'users'> typed userId which won't match existing rows where
profiles.userId was stored as the legacy identity.subject string; change
getCurrentProfile (and/or the lookup logic) to first attempt the lookup with the
typed stable userId returned by getStableUserId and, if that returns no result,
perform a fallback lookup using the legacy string form (String(userId) or the
original identity.subject) so both existing profiles and new typed IDs are
matched when querying the 'by_userId' index.

In `@docs/SCHEMA_MIGRATION.md`:
- Around line 134-138: The docs incorrectly say v.string() fields hold "Auth
identities (strings from identity.subject)"; update the summary to say these
fields store stable Convex user document IDs (Id<'users'>) as returned by
getAuthUserId and requireAuthUserId in the implementation, and change any
wording that implies raw auth subject strings to instead reference Convex user
IDs / document references so the documentation matches the actual code behavior.
- Around line 45-51: The "After" example contradicts the warning by using
identity.subject directly; replace that line so the example calls
requireAuthUserId(ctx) to obtain a stable, type-safe userId and then assigns
that value to sellerId instead of using identity.subject; update the snippet to
reference requireAuthUserId and sellerId (and remove the unsafe cast example or
keep it as the "Before" example) so the docs demonstrate using requireAuthUserId
for ownership keys.

---

Nitpick comments:
In `@backend/convex/messages.ts`:
- Around line 30-36: The wrapper function requireStableUserId simply forwards to
requireAuthUserId without adding behavior; remove requireStableUserId and
replace its usages with direct calls to requireAuthUserId (e.g., replace
requireStableUserId(ctx) with requireAuthUserId(ctx) or requireAuthUserId(ctx,
'Unauthorized') as needed), or if you prefer keeping it, add a comment
documenting its intended future purpose—update any imports/exports and tests
referencing requireStableUserId accordingly.

In `@backend/convex/reports.ts`:
- Around line 25-100: The inline comments in reports.ts around the report
creation flow (e.g., the comment blocks referencing validation, target
existence, duplicate check, rate limiting, create report, and auto-hide logic
that surround symbols like MAX_NOTES_LENGTH, ctx.db.get, existingReport,
recentReports, reportId, and allReports) use inconsistent numbering
(2,5,6,6,7,8); remove the numeric prefixes or renumber them sequentially (1..N)
so the steps read clearly (e.g., "1. Validate notes length", "2. Validate target
exists", "3. Check duplicate report", etc.), and update or remove any duplicate
"6." comment to keep the sequence consistent.

In `@frontend/app/`(tabs)/index.tsx:
- Around line 342-369: The ListEmptyComponent currently wraps the ScreenState
JSX with an unnecessary conditional check on listings.length === 0; remove that
redundant condition and return the ScreenState wrapper directly from
ListEmptyComponent so FlatList can control when it's shown. Specifically, update
the ListEmptyComponent prop to render the <View
style={styles.stateContainer}><ScreenState ... /></View> unconditionally
(keeping all props: variant, title, message, actionLabel, onRetry ->
refreshListings, onAction -> handleClearAll) and remove the outer ternary that
checks listings.length; no other logic changes to ScreenState, handleClearAll,
or refreshListings are needed.

In `@frontend/components/ListingCard.tsx`:
- Around line 296-307: Remove the no-op style objects listingTitleHome and
listingPriceHome from the styles object in ListingCard.tsx and clean up any
references in the JSX so you only apply existing styles (e.g., listingTitle,
listingPrice) — where conditional application was used, change it to only
include the style if it exists (or remove the conditional spread) so JSX doesn't
reference the removed keys; update any className/style arrays to not include
listingTitleHome/listingPriceHome.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8dc88b8e-bed0-43c9-ada0-85e6afd0305e

📥 Commits

Reviewing files that changed from the base of the PR and between a963946 and b09522c.

⛔ Files ignored due to path filters (1)
  • backend/convex/_generated/api.d.ts is excluded by !**/_generated/**
📒 Files selected for processing (16)
  • backend/convex/__tests__/messages.test.ts
  • backend/convex/__tests__/profiles.test.ts
  • backend/convex/lib/authIdentity.ts
  • backend/convex/listings.ts
  • backend/convex/messages.ts
  • backend/convex/profiles.ts
  • backend/convex/pushNotifications.ts
  • backend/convex/reports.ts
  • backend/convex/savedListings.ts
  • docs/SCHEMA_MIGRATION.md
  • frontend/app/(tabs)/index.tsx
  • frontend/app/auth/login.tsx
  • frontend/app/conversations/[id].tsx
  • frontend/components/ListingCard.tsx
  • frontend/components/TagInput.tsx
  • frontend/components/TagPicker.tsx

Comment on lines +107 to +114
const isBuyer = convo.buyerId === userId;
const isSeller = convo.sellerId === userId;
if (!isBuyer && !isSeller) {
throw new ConvexError('Forbidden');
}

const senderId = isBuyerByAlias ? convo.buyerId : convo.sellerId;
const recipientId = isBuyerByAlias ? convo.sellerId : convo.buyerId;
const senderId = userId;
const recipientId = isBuyer ? convo.sellerId : convo.buyerId;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Permission checks will fail for existing conversations.

The buyer/seller comparison convo.buyerId === userId will fail for existing conversations where buyerId is stored as identity.subject. Users will receive "Forbidden" errors when trying to send messages in their existing conversations.

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

In `@backend/convex/messages.ts` around lines 107 - 114, The permission check
fails because convo.buyerId / convo.sellerId may be stored as an identity object
(e.g., { subject }) while userId is a string; update the checks to normalize
both participant IDs before comparing: derive normalizedBuyerId and
normalizedSellerId (e.g., const normalizedBuyerId = typeof convo.buyerId ===
'string' ? convo.buyerId : convo.buyerId?.subject) and use those in the
isBuyer/isSeller checks and when computing recipientId and senderId (replace
references to convo.buyerId/convo.sellerId with the normalized variables) so
existing conversations with identity objects pass the permission check.

Comment on lines +212 to +225
const userId = await requireStableUserId(ctx);

const [buyerConversations, sellerConversations] = await Promise.all([
ctx.db
.query('conversations')
.withIndex('by_buyer', (q) => q.eq('buyerId', userId))
.order('desc')
.collect(),
ctx.db
.query('conversations')
.withIndex('by_seller', (q) => q.eq('sellerId', userId))
.order('desc')
.collect(),
]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Breaking change: existing conversations will not be found.

The listUserConversations query uses by_buyer and by_seller indexes with userId (now Id<'users'>). Existing conversations stored with buyerId/sellerId = identity.subject will not be matched.

Impact: Users will see an empty inbox despite having existing conversations.

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

In `@backend/convex/messages.ts` around lines 212 - 225, The listUserConversations
query now uses requireStableUserId which returns Id<'users'>, but existing
conversations were stored with buyerId/sellerId = identity.subject (legacy
string), so queries against the by_buyer and by_seller indexes miss existing
rows; update listUserConversations to query both id formats (the new stableId
from requireStableUserId and the legacy subject from ctx.auth.identity.subject)
for buyerId and sellerId (e.g., run queries for both values or use an
OR/IN-equivalent and merge/deduplicate results), referencing the
functions/values requireStableUserId, listUserConversations, by_buyer,
by_seller, buyerId, sellerId, and identity.subject so existing conversations are
returned.

Comment on lines 139 to 145
const existingProfile = await ctx.db
.query('profiles')
.withIndex('by_userId', (q) => q.eq('userId', identity.subject))
.withIndex('by_userId', (q) => q.eq('userId', userId))
.unique();
if (existingProfile) {
throw new ConvexError('Profile already exists for this user');
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Duplicate profile guard bypassed for existing users.

The duplicate check queries by userId (now Id<'users'>). If a user's existing profile was stored with identity.subject, this check will not find it, allowing the user to create a second profile.

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

In `@backend/convex/profiles.ts` around lines 139 - 145, The duplicate-profile
check only queries by userId and misses profiles stored under identity.subject,
so update the check to look for either identifier: query profiles by userId
(existingProfile) AND also query profiles by subject (e.g., identity.subject)
and throw if either query returns a record; if there isn't already an index for
subject, add or use an index such as 'by_subject' and call
ctx.db.query('profiles').withIndex('by_subject', q => q.eq('subject',
identity.subject)) (or run both queries in the createProfile function) to ensure
users cannot create a second profile.

@evan-taylor evan-taylor merged commit dc8ee64 into dev Mar 11, 2026
3 checks passed
@evan-taylor evan-taylor deleted the evan/fixes branch March 11, 2026 07:34
evan-taylor added a commit that referenced this pull request Apr 9, 2026
* implemented CRUD operations in backend

* feat: implement Cal Poly email authentication with verification

- Add user authentication using @convex-dev/auth with Password provider
- Enforce @calpoly.edu email domain validation
- Implement email verification flow with SendGrid integration
- Create signup, login, and email verification screens
- Add user schema and auth-related queries/mutations
- Update shared types and utilities for email validation
- Add AuthProvider to app layout with auth routes

* docs: update environment variables documentation for auth

* fix: secure email verification with token validation

* feat: implemented profile viewing and editing

* feat: add search and filtering for listings
- Add condition field to listings (new, like_new, good, fair, poor)
- Add searchAndFilterListings query with full-text search
- Support category, price range, condition filters
- Add sort options and cursor-based pagination (20 items/page)
- Add compound indexes for performance

* refactor: switch from Password+SendGrid to OTP+Resend
- Replace Password provider with Resend OTP (8-digit code, 15 min expiry)
- Add Cal Poly email domain validation
- Simplify to single login screen (email → code)
- Remove separate signup and verify-email screens
- Switch from SendGrid to Resend for emails
- Use @convex-dev/auth OTP pattern per reviewer feedback

* Adding .env.examples back

* fix: address CodeRabbit review suggestions for auth implementation

* fix: configure ESM module resolution for backend typecheck

* Normalize email to lowercase and trim whitespace

* fix: added constraints to profile creation

* POLY-10 add messaging and conversation schema and sendMessage mutation

* POLY-10 test function for sendMessage mutation

* POLY-10 feat: add retrieve conversation history query

* POLY-10 feat: add listUserConversations query

* POLY-10 fix: updated commenting and edited listingId in schema

* feat: associate conversations with listings, read functionality, real-time message delivery

* Sync Convex schema, generated files, and backend dependencies

* POLY-10: Fix functions for updated schema

* Add pagination to getProfiles query

Updated getProfiles query to support pagination options.

* Update package.json

* Update convex dependency version to 1.31.5

* Update package.json

* Update package.json

* Downgrade convex dependency version to 1.17.2

* this should fix it

* there we go

* feat: implement custom OTP authentication backend (POLY-29)
- Add otpCodes table with hashing and rate limiting
- Implement requestOTP action with Cal Poly email validation
- Implement verifyOTP mutation with attempt limiting
- Replace @convex-dev/auth with custom OTP system
- Add bcryptjs for secure OTP hashing
- Integrate Resend for email delivery
- Add emailVerified field to users table
BREAKING CHANGE: Replaces @convex-dev/auth authentication system

* chore: update package-lock.json for bcryptjs dependencies

* fix: implement CodeRabbit security improvements

- Make createAndStoreOTPInternal an internalMutation to prevent client access
- Implement atomic rate-limit check and OTP storage in single transaction
- Replace Math.random() with crypto.getRandomValues() for secure OTP generation
- Add emailVerified field to User type to match backend schema

Security improvements prevent race conditions and ensure cryptographically
secure random number generation for OTP codes.

* fix: make emailVerified optional for backward compatibility

Make emailVerified field optional in schema and User type to prevent
breaking existing user records that don't have this field. New users
created via OTP will have emailVerified set to true, while existing
users can be migrated gradually without validation errors.

* feat: add report schema and submission endpoint (POLY-19)

- Add reports table to Convex schema with indexes for target and reporter lookups
- Implement createReport mutation with authentication, validation, and duplicate prevention
- Add rate limiting (10 reports per 24 hours per user)
- Create comprehensive test suite with 9 tests covering all acceptance criteria
- Ensure reporterId is never exposed in responses

* Fix(POLY-19): Address test failures and code review feedback & Feat(POLY-20): Implement auto-hide logic

* fix: enforce auth in messaging queries and make debug mutation internal

* feat: added tags field with validation

* fix: fixed updateListing test cases

* fix: add AsyncStorage for React Native auth storage

* Fix(POLY-20): Prevent auto-hide from overwriting existing hidden context

* feat(POLY-15): Add home feed sorting and filtering

- Update getListings query with category, minPrice, maxPrice filters
- Use by_status_createdAt index for deterministic newest-first ordering
- Add price validation (min >= 0, max >= min)
- Add FilterBar component with filter chips
- Add CategoryPicker bottom sheet
- Add PriceRangePicker bottom sheet with presets
- Integrate filters into home screen with state management
- Add 'No results' state with clear filters option
- Add 7 tests for filter combinations

* fix(POLY-15): address code review feedback

- Refactor FilterBar to avoid nested TouchableOpacity
- Add negative max price validation to PriceRangePicker

* fix(POLY-15): add NaN/non-finite validation for price inputs

- Trim whitespace from inputs before parsing
- Treat empty/whitespace-only strings as undefined
- Validate parsed values are finite with Number.isFinite
- Show clear error message for invalid number input

* done

* done now

* other issues done too

* other issues done too

* fixed errors

* feat: add tag filters to getListings

* fix: pass empty args to getListings useQuery

* fix: modify listings.ts to pass local tests and ran npm install

* fixed final errors

* fixed final errors

* fixed new  errors

* fixed new  errors

* fixed

* Poly-16-Tags-Backend

* removed redundancy

* fix

* fix

* fix

* Fix2

* fix: integrate coderabbit changes and edit testcases

* fix: filter listings by tags in memory

* feat: exclude hidden listings from getListings

* Add validation for limit and price parameters in getListings query

* Fix duplicate reports table in schema and update dependencies

* Merge tag filtering with category/price filters

- Add tag filtering to getListings query (OR logic)
- Add category and price range filters
- Create unified FilterBar with all three filter types
- Add CategoryPicker and PriceRangePicker components
- Support URL params for tag deep linking
- Add comprehensive validation for price inputs

* refactor: address code review feedback

- Remove redundant arrayContaining assertion in schema test
- Extract shared filter types to frontend/types/filters.ts
- Remove duplicate CATEGORIES from CategoryPicker
- Fix PriceRangePicker useEffect to only reset on modal open
- Remove dead padding properties from chip style
- Fix URL param sync in handleTagsChange

* Fix duplicate useState import and unused styles in index.tsx

* fix: exclude hidden listings from getListings query

* fix: resolve merge conflicts and fix test configuration

- Merge tag filtering tests with basic filtering tests from dev
- Add @babel/preset-typescript to babel.config.js for proper TS support
- Configure jest to use ts-jest for .ts files and babel-jest for .js files
- Add price validation to getListings (minPrice/maxPrice checks)
- Add getCurrentUserSubject query from dev branch
- Fix test ordering by using _creationTime instead of createdAt
- All 55 tests now passing

* fix: remove duplicate functions from merge conflict

- Remove duplicate getListings export (kept complete version with tag filtering)
- Remove duplicate getCurrentUserSubject export
- Remove unused normalizeTags function
- All tests passing, linting clean

* fix: remove duplicate listings variable declaration

- Remove unfiltered listings query on line 13
- Keep filtered listings query that respects filter state
- Fixes TypeScript error: Cannot redeclare block-scoped variable

* fixed redundancy of validate/normalize

* chore: upgrade Expo SDK to 54 and fix dependencies

- Upgrade Expo from ~52.0.0 to ^54.0.33
- Upgrade React to 19.1.0 and React Native to 0.81.5
- Update all Expo packages to SDK 54 compatible versions
- Add @auth/core dependency to backend to fix convex dev
- Add .npmrc with legacy-peer-deps flag to handle peer dependency conflicts
- Fix npm audit vulnerabilities

* chore: add self-hosted Convex actions URL to environment configuration

* testing

* fixes

* add docs for s3 convex config (#38)

* fixes

* delete

* fixes

* fixes

* fixes

* fixes

* test added

* fixes

* update

* tests

* feat: integrate OpenAI Moderation API for listings and messages

- Add moderationResults table to schema
- Create moderation.ts with moderateContent internalAction
- Refactor createListing/updateListing from mutations to actions
- Refactor sendMessage from mutation to action
- Add ConvexError responses for flagged content
- Update all test files for action-based API with fetch mocks
- Disable ts-jest diagnostics for convex-test ID serialization
- Document OPENAI_API_KEY in .env.example

Resolves: POLY-31, POLY-36, POLY-37

* fix: switch createListing/updateListing from useMutation to useAction in frontend

Frontend was still using useMutation for createListing and updateListing,
which are now actions after moderation integration. This caused TypeScript
errors: FunctionReference<"action"> not assignable to FunctionReference<"mutation">.

* fix: extract ConvexError.data for moderation rejection messages

ConvexError stores the custom error string in .data, not .message.
Without this fix, moderation rejection messages would not surface
to the user in the Alert dialog.

* feat: exclude hidden content from feed and handle hidden states

* fix: code rabbit changes

* WIP: messaging changes

* chore: make POLY-38 schema rollout backward-compatible with backfill

* feat: clean shareable links route and listing share action

* feat: add production-ready report modal flow for listing detail

* style: format ReportModal with Prettier

* feat: rebuild clean image uploader with upload URL flow and timeout safeguards

* feat: deep-linking listings rebased from nightly clean on latest dev

* feat: expo upgrade, fix config, enhance ui, add animations

- Deleted Babel configuration file as it is no longer needed.
- Updated Jest configuration to specify Babel config file path.
- Added environment variables for email handling in backend.
- Enhanced email validation and error handling in ResendOTP function.
- Improved frontend components with better loading states and animations.
- Updated dependencies in package-lock.json for compatibility with new Expo version.

* refactor: streamline auth configuration and improve search functionality

- Updated auth configuration to use a unified provider domain from environment variables.
- Simplified search component logic to enhance performance and maintain reactivity for first-page results.
- Removed unnecessary cursor handling for improved clarity in state management.

* chore: update expo-image-manipulator and expo-image-picker versions in package files

- Upgraded expo-image-manipulator to version 55.0.9 and expo-image-picker to version 55.0.10 in both package-lock.json and package.json for improved functionality and compatibility.

* fix: coderabbit feedback

- Added a function to dynamically determine the app origin for shareable links in the ListingDetailScreen.
- Updated the cancel button in both NewListingScreen and EditListingScreen to be disabled during submissions or pending uploads, with visual feedback for disabled state.

* feat: enhance login functionality with return redirection

- Integrated useLocalSearchParams to retrieve returnTo parameter for post-auth redirection.
- Updated router.replace to redirect users to the specified return path after successful login, improving user experience.

* feat: enhance login screen with verification step handling

- Introduced a new state variable to manage the verification step in the login process.
- Updated the rendering logic to display success messages conditionally based on the verification step, improving user feedback during authentication.

* chore: update dependencies and enhance TypeScript configuration

- Added expo-router version 55.0.3 to package.json and package-lock.json for improved routing capabilities.
- Updated TypeScript configuration in tsconfig.json to include additional file types and improve module resolution.

* feat: enhance listings and profiles management

- Updated the updateListing action to return specific moderation error responses instead of throwing exceptions.
- Added a new query to retrieve the current user's listings, including hidden and inactive items.
- Introduced a new query to fetch the current authenticated user's full profile, including non-public fields.
- Enhanced profile creation and update mutations to include additional fields and validation for email, year, and other profile attributes.
- Improved error handling in listing creation and editing to provide user-friendly feedback for moderation issues.
- Updated frontend components to reflect new profile and listing functionalities, including loading states and input validations.

* refactor: improve error messaging in profile creation tests

- Updated the test case for createProfile to clarify rejection conditions when no email is provided or available on identity.
- Enhanced error message to be more user-friendly, specifying that an email is required to create a profile.

* chore: update Node.js version in configuration files

* fix: ci script

* fix: ci checks

* fix: ci linux lightningcss binary

* fixed issue

* fixed issue

* potential redundent querires

* null request bug

* feat: implement push notifications and update dependencies

- Added support for push notifications using @convex-dev/expo-push-notifications.
- Updated the convex configuration to include push notification handling in message actions.
- Enhanced tests to verify push notification functionality upon message creation.
- Updated dependencies, including upgrading convex to version 1.32.0 and related Expo packages.
- Modified configuration files to reflect changes in app identifiers and notification settings.

* fix: improve push notification handling and enhance tests

- Updated the sendMessage action to use a more structured approach for push notifications based on the environment.
- Enhanced test setup for push notifications by refactoring the mock implementation for better clarity and maintainability.
- Added error handling for push notification failures during message delivery.
- Updated the useAuth hook to ensure push tokens are removed on sign-out, improving token management.

* fix: update app slug for consistency

* feat: add message seller button on listings

* feat: create inbox screen

* feat: create chat screen

* feat: implement read receipts for inbox in nav bar

* fix: remove errors when clicking inbox

* fix: prevent duplicate conversations

* feat: add read receipts to messages

* fix: show buyer name for listing creator

* fix: implemented code rabbit changes

* fix: add more code rabbit changes

* Development environment setup (#55)

* docs: add AGENTS.md with Cursor Cloud development instructions

* docs: update AGENTS.md with Convex cloud setup and login caveat

* docs: update AGENTS.md to reflect Convex cloud only (no self-hosted)

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>

* feat: UI overhaul (#57)

* feat: add saved listings functionality and enhance listings queries

- Implemented `savedListings` table with associated queries and mutations for saving, unsaving, and checking saved listings.
- Added `getListingsBySeller` query to fetch public active listings by seller.
- Updated `updateListingStatus` mutation to prevent changing status of sold listings.
- Enhanced tests for new saved listings features and updated existing tests for listing status changes.
- Improved UI components to support new functionalities and ensure better user experience.

* fix: agent bugs

* feat: integrate Sentry for error tracking and monitoring

- Added `@sentry/react-native` dependency to the project.
- Initialized Sentry in the application with the provided DSN for error tracking.
- Updated `app.json` to include Sentry configuration for Expo.
- Enhanced `_layout.tsx` to wrap the `RootLayout` component with Sentry for better error handling.

* refactor: update Metro configuration for Sentry integration

* feat: enhance authentication and profile management

- Introduced AppReviewOTP for Apple App Review email verification.
- Updated authentication flow to support specific email validation for the review process.
- Refactored profile bounds to a shared constants module for better maintainability.
- Improved error handling and user feedback in login and profile edit screens.
- Enhanced saved listings functionality to mark deleted and hidden listings as unavailable.
- Updated tests to cover new email validation logic and recent changes in saved listings.

* feat: add optional App Review OTP configuration

- Introduced optional App Review OTP functionality for Apple App Review login.
- Updated backend and frontend to support dynamic email and code configuration via environment variables.
- Enhanced email validation logic in the login process to accommodate the new OTP feature.
- Updated documentation to reflect the new configuration options for App Review.

* fix: icon and package mismatch (#59)

* refactor: unify authentication handling across backend modules (#60)

- Replaced direct calls to `getUserIdentity` with `requireAuthUserId` in various modules to ensure consistent user authentication checks.
- Updated ownership and user ID retrieval logic in listings, messages, profiles, and saved listings to utilize stable user IDs.
- Enhanced error handling for unauthorized actions across multiple mutations and queries.
- Removed legacy participant ID handling in messages and conversations to streamline user identification.
- Improved overall code maintainability and clarity by consolidating authentication logic.

* feat: enhance My Listings screen with status filtering and action han… (#61)

* feat: enhance My Listings screen with status filtering and action handling

- Added status filter options ('all', 'active', 'inactive', 'sold') to the My Listings screen for better listing management.
- Implemented action handling for editing, marking as sold, setting status, and deleting listings through a new action sheet.
- Improved error handling with user-friendly alerts for various actions.
- Updated UI components to reflect changes in listing management and enhance user experience.

* feat: unify authentication redirection across screens

- Implemented consistent use of `router.replace` for authentication redirection in various screens including Inbox, Home, My Listings, Search, Settings, and Listing Details.
- Enhanced user experience by replacing sign-in prompts with loading indicators while redirecting to the login page.
- Improved accessibility labels in ListingCard for better screen reader support.

* feat: implement messaging block checks and notification preferences (#62)

* feat: implement messaging block checks and notification preferences

- Added functionality to prevent users from messaging each other if they have blocked one another, enhancing user experience and safety.
- Introduced a new mutation to manage message notification preferences, allowing users to enable or disable notifications for incoming messages.
- Updated the user schema to include a field for message notification settings.
- Enhanced the login flow to prompt users for notification permissions, improving engagement with the messaging feature.
- Added tests to ensure proper functionality of blocking and notification features.

* chore: update package dependencies and app configuration

- Updated various Expo package versions in package-lock.json and package.json for improved stability and features.
- Added iOS icon path in app.json to enhance app branding on iOS devices.

* feat: enhance user blocking functionality and notification handling

- Implemented a new query to check if the current user is blocked by another user, improving user experience in conversations.
- Updated the blockUser mutation to ensure proper user ID normalization and error handling for blocking actions.
- Enhanced the Settings screen to provide better feedback during account deletion and sign-out processes.
- Refactored notification handling to ensure users are informed about permission status and potential issues when enabling notifications.
- Improved conversation detail screen to handle blocking states more effectively, preventing users from sending messages if blocked.

* feat: improve user blocking and notification preference handling

- Introduced a new function to validate peer user IDs in blocking mutations, enhancing error handling for user blocking actions.
- Updated blockUser and unblockUser mutations to utilize the new validation function, ensuring proper user ID normalization.
- Enhanced Settings screen to provide detailed alerts for notification preference updates, improving user feedback during push token management.
- Improved conversation detail screen to handle blocking states more effectively, preventing message sending if users are blocked.

* chore: update app configuration and package scripts

- Removed iOS icon path from app.json to streamline configuration.
- Updated package.json scripts for running Android and iOS to use 'expo run' commands, enhancing build process consistency.

* feat: transition to Convex Cloud and update environment configurations (#53)

* feat: transition to Convex Cloud and update environment configurations

- Updated backend configuration to use Convex Cloud instead of self-hosted setup.
- Revised environment variable instructions in documentation for backend and frontend.
- Enhanced the My Listings screen with delete functionality and improved user feedback.
- Updated various documentation files to reflect the migration to Convex Cloud and removed references to the legacy self-hosted setup.

* docs: update README and contributing guide for local development setup

- Added instructions for local setup without a Convex account in README.md.
- Revised contributing guide to clarify environment variable configuration options for both Convex Cloud and local development.
- Removed outdated Convex Cloud migration documentation and legacy self-hosted references.

* fix: preserve settings route typing after rebase

* fix: address Convex migration and settings review notes

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>

* Improve messaging safety and polish the web experience (#63)

* chore: update package dependencies and app configuration

- Updated various Expo package versions in package-lock.json and package.json for improved stability and features.
- Added iOS icon path in app.json to enhance app branding on iOS devices.

* chore: update app configuration and package scripts

- Removed iOS icon path from app.json to streamline configuration.
- Updated package.json scripts for running Android and iOS to use 'expo run' commands, enhancing build process consistency.

* feat: polish the Vercel web experience

Make the Expo web app feel intentional on desktop while steering app-only flows back to mobile.
Add Vercel-friendly config so preview and production builds resolve the right web origin.

* fix: remove duplicate layout import after rebase

* fix: clean up search list layout styles

* fix: preserve web search state

Keep the resolved app origin available at runtime and preserve active listing filters when web search updates or refreshes.

* chore: manual GitHub Action for dev → main release PR (#64)

* chore: update package dependencies and app configuration

- Updated various Expo package versions in package-lock.json and package.json for improved stability and features.
- Added iOS icon path in app.json to enhance app branding on iOS devices.

* chore: update app configuration and package scripts

- Removed iOS icon path from app.json to streamline configuration.
- Updated package.json scripts for running Android and iOS to use 'expo run' commands, enhancing build process consistency.

* feat: polish the Vercel web experience

Make the Expo web app feel intentional on desktop while steering app-only flows back to mobile.
Add Vercel-friendly config so preview and production builds resolve the right web origin.

* fix: remove duplicate layout import after rebase

* fix: clean up search list layout styles

* fix: preserve web search state

Keep the resolved app origin available at runtime and preserve active listing filters when web search updates or refreshes.

* chore: add manual GitHub Action to open dev→main release PR

- workflow_dispatch only; idempotent open PR or report existing
- document usage in contributing releases section

---------

Co-authored-by: dfed25 <domfederico21@gmail.com>
Co-authored-by: Jaydon Chen <79879038+jaydonkc@users.noreply.github.com>
Co-authored-by: Cole <hackman@calpoly.edu>
Co-authored-by: Haixin <haixinhuang502@gmail.com>
Co-authored-by: BoB121isawesome <79879038+BoB121isawesome@users.noreply.github.com>
Co-authored-by: lheutchy <lheutchy@gmail.com>
Co-authored-by: dfed25 <150391626+dfed25@users.noreply.github.com>
Co-authored-by: MatthewPhan <Matthewminhphan@gmail.com>
Co-authored-by: SamanSP1386 <saman.sepehr86@gmail.com>
Co-authored-by: Jaydon Bot <jaydon-bot@local>
Co-authored-by: Taye-Staats <tayestaats@outlook.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
evan-taylor added a commit that referenced this pull request Apr 9, 2026
* implemented CRUD operations in backend

* feat: implement Cal Poly email authentication with verification

- Add user authentication using @convex-dev/auth with Password provider
- Enforce @calpoly.edu email domain validation
- Implement email verification flow with SendGrid integration
- Create signup, login, and email verification screens
- Add user schema and auth-related queries/mutations
- Update shared types and utilities for email validation
- Add AuthProvider to app layout with auth routes

* docs: update environment variables documentation for auth

* fix: secure email verification with token validation

* feat: implemented profile viewing and editing

* feat: add search and filtering for listings
- Add condition field to listings (new, like_new, good, fair, poor)
- Add searchAndFilterListings query with full-text search
- Support category, price range, condition filters
- Add sort options and cursor-based pagination (20 items/page)
- Add compound indexes for performance

* refactor: switch from Password+SendGrid to OTP+Resend
- Replace Password provider with Resend OTP (8-digit code, 15 min expiry)
- Add Cal Poly email domain validation
- Simplify to single login screen (email → code)
- Remove separate signup and verify-email screens
- Switch from SendGrid to Resend for emails
- Use @convex-dev/auth OTP pattern per reviewer feedback

* Adding .env.examples back

* fix: address CodeRabbit review suggestions for auth implementation

* fix: configure ESM module resolution for backend typecheck

* Normalize email to lowercase and trim whitespace

* fix: added constraints to profile creation

* POLY-10 add messaging and conversation schema and sendMessage mutation

* POLY-10 test function for sendMessage mutation

* POLY-10 feat: add retrieve conversation history query

* POLY-10 feat: add listUserConversations query

* POLY-10 fix: updated commenting and edited listingId in schema

* feat: associate conversations with listings, read functionality, real-time message delivery

* Sync Convex schema, generated files, and backend dependencies

* POLY-10: Fix functions for updated schema

* Add pagination to getProfiles query

Updated getProfiles query to support pagination options.

* Update package.json

* Update convex dependency version to 1.31.5

* Update package.json

* Update package.json

* Downgrade convex dependency version to 1.17.2

* this should fix it

* there we go

* feat: implement custom OTP authentication backend (POLY-29)
- Add otpCodes table with hashing and rate limiting
- Implement requestOTP action with Cal Poly email validation
- Implement verifyOTP mutation with attempt limiting
- Replace @convex-dev/auth with custom OTP system
- Add bcryptjs for secure OTP hashing
- Integrate Resend for email delivery
- Add emailVerified field to users table
BREAKING CHANGE: Replaces @convex-dev/auth authentication system

* chore: update package-lock.json for bcryptjs dependencies

* fix: implement CodeRabbit security improvements

- Make createAndStoreOTPInternal an internalMutation to prevent client access
- Implement atomic rate-limit check and OTP storage in single transaction
- Replace Math.random() with crypto.getRandomValues() for secure OTP generation
- Add emailVerified field to User type to match backend schema

Security improvements prevent race conditions and ensure cryptographically
secure random number generation for OTP codes.

* fix: make emailVerified optional for backward compatibility

Make emailVerified field optional in schema and User type to prevent
breaking existing user records that don't have this field. New users
created via OTP will have emailVerified set to true, while existing
users can be migrated gradually without validation errors.

* feat: add report schema and submission endpoint (POLY-19)

- Add reports table to Convex schema with indexes for target and reporter lookups
- Implement createReport mutation with authentication, validation, and duplicate prevention
- Add rate limiting (10 reports per 24 hours per user)
- Create comprehensive test suite with 9 tests covering all acceptance criteria
- Ensure reporterId is never exposed in responses

* Fix(POLY-19): Address test failures and code review feedback & Feat(POLY-20): Implement auto-hide logic

* fix: enforce auth in messaging queries and make debug mutation internal

* feat: added tags field with validation

* fix: fixed updateListing test cases

* fix: add AsyncStorage for React Native auth storage

* Fix(POLY-20): Prevent auto-hide from overwriting existing hidden context

* feat(POLY-15): Add home feed sorting and filtering

- Update getListings query with category, minPrice, maxPrice filters
- Use by_status_createdAt index for deterministic newest-first ordering
- Add price validation (min >= 0, max >= min)
- Add FilterBar component with filter chips
- Add CategoryPicker bottom sheet
- Add PriceRangePicker bottom sheet with presets
- Integrate filters into home screen with state management
- Add 'No results' state with clear filters option
- Add 7 tests for filter combinations

* fix(POLY-15): address code review feedback

- Refactor FilterBar to avoid nested TouchableOpacity
- Add negative max price validation to PriceRangePicker

* fix(POLY-15): add NaN/non-finite validation for price inputs

- Trim whitespace from inputs before parsing
- Treat empty/whitespace-only strings as undefined
- Validate parsed values are finite with Number.isFinite
- Show clear error message for invalid number input

* done

* done now

* other issues done too

* other issues done too

* fixed errors

* feat: add tag filters to getListings

* fix: pass empty args to getListings useQuery

* fix: modify listings.ts to pass local tests and ran npm install

* fixed final errors

* fixed final errors

* fixed new  errors

* fixed new  errors

* fixed

* Poly-16-Tags-Backend

* removed redundancy

* fix

* fix

* fix

* Fix2

* fix: integrate coderabbit changes and edit testcases

* fix: filter listings by tags in memory

* feat: exclude hidden listings from getListings

* Add validation for limit and price parameters in getListings query

* Fix duplicate reports table in schema and update dependencies

* Merge tag filtering with category/price filters

- Add tag filtering to getListings query (OR logic)
- Add category and price range filters
- Create unified FilterBar with all three filter types
- Add CategoryPicker and PriceRangePicker components
- Support URL params for tag deep linking
- Add comprehensive validation for price inputs

* refactor: address code review feedback

- Remove redundant arrayContaining assertion in schema test
- Extract shared filter types to frontend/types/filters.ts
- Remove duplicate CATEGORIES from CategoryPicker
- Fix PriceRangePicker useEffect to only reset on modal open
- Remove dead padding properties from chip style
- Fix URL param sync in handleTagsChange

* Fix duplicate useState import and unused styles in index.tsx

* fix: exclude hidden listings from getListings query

* fix: resolve merge conflicts and fix test configuration

- Merge tag filtering tests with basic filtering tests from dev
- Add @babel/preset-typescript to babel.config.js for proper TS support
- Configure jest to use ts-jest for .ts files and babel-jest for .js files
- Add price validation to getListings (minPrice/maxPrice checks)
- Add getCurrentUserSubject query from dev branch
- Fix test ordering by using _creationTime instead of createdAt
- All 55 tests now passing

* fix: remove duplicate functions from merge conflict

- Remove duplicate getListings export (kept complete version with tag filtering)
- Remove duplicate getCurrentUserSubject export
- Remove unused normalizeTags function
- All tests passing, linting clean

* fix: remove duplicate listings variable declaration

- Remove unfiltered listings query on line 13
- Keep filtered listings query that respects filter state
- Fixes TypeScript error: Cannot redeclare block-scoped variable

* fixed redundancy of validate/normalize

* chore: upgrade Expo SDK to 54 and fix dependencies

- Upgrade Expo from ~52.0.0 to ^54.0.33
- Upgrade React to 19.1.0 and React Native to 0.81.5
- Update all Expo packages to SDK 54 compatible versions
- Add @auth/core dependency to backend to fix convex dev
- Add .npmrc with legacy-peer-deps flag to handle peer dependency conflicts
- Fix npm audit vulnerabilities

* chore: add self-hosted Convex actions URL to environment configuration

* testing

* fixes

* add docs for s3 convex config (#38)

* fixes

* delete

* fixes

* fixes

* fixes

* fixes

* test added

* fixes

* update

* tests

* feat: integrate OpenAI Moderation API for listings and messages

- Add moderationResults table to schema
- Create moderation.ts with moderateContent internalAction
- Refactor createListing/updateListing from mutations to actions
- Refactor sendMessage from mutation to action
- Add ConvexError responses for flagged content
- Update all test files for action-based API with fetch mocks
- Disable ts-jest diagnostics for convex-test ID serialization
- Document OPENAI_API_KEY in .env.example

Resolves: POLY-31, POLY-36, POLY-37

* fix: switch createListing/updateListing from useMutation to useAction in frontend

Frontend was still using useMutation for createListing and updateListing,
which are now actions after moderation integration. This caused TypeScript
errors: FunctionReference<"action"> not assignable to FunctionReference<"mutation">.

* fix: extract ConvexError.data for moderation rejection messages

ConvexError stores the custom error string in .data, not .message.
Without this fix, moderation rejection messages would not surface
to the user in the Alert dialog.

* feat: exclude hidden content from feed and handle hidden states

* fix: code rabbit changes

* WIP: messaging changes

* chore: make POLY-38 schema rollout backward-compatible with backfill

* feat: clean shareable links route and listing share action

* feat: add production-ready report modal flow for listing detail

* style: format ReportModal with Prettier

* feat: rebuild clean image uploader with upload URL flow and timeout safeguards

* feat: deep-linking listings rebased from nightly clean on latest dev

* feat: expo upgrade, fix config, enhance ui, add animations

- Deleted Babel configuration file as it is no longer needed.
- Updated Jest configuration to specify Babel config file path.
- Added environment variables for email handling in backend.
- Enhanced email validation and error handling in ResendOTP function.
- Improved frontend components with better loading states and animations.
- Updated dependencies in package-lock.json for compatibility with new Expo version.

* refactor: streamline auth configuration and improve search functionality

- Updated auth configuration to use a unified provider domain from environment variables.
- Simplified search component logic to enhance performance and maintain reactivity for first-page results.
- Removed unnecessary cursor handling for improved clarity in state management.

* chore: update expo-image-manipulator and expo-image-picker versions in package files

- Upgraded expo-image-manipulator to version 55.0.9 and expo-image-picker to version 55.0.10 in both package-lock.json and package.json for improved functionality and compatibility.

* fix: coderabbit feedback

- Added a function to dynamically determine the app origin for shareable links in the ListingDetailScreen.
- Updated the cancel button in both NewListingScreen and EditListingScreen to be disabled during submissions or pending uploads, with visual feedback for disabled state.

* feat: enhance login functionality with return redirection

- Integrated useLocalSearchParams to retrieve returnTo parameter for post-auth redirection.
- Updated router.replace to redirect users to the specified return path after successful login, improving user experience.

* feat: enhance login screen with verification step handling

- Introduced a new state variable to manage the verification step in the login process.
- Updated the rendering logic to display success messages conditionally based on the verification step, improving user feedback during authentication.

* chore: update dependencies and enhance TypeScript configuration

- Added expo-router version 55.0.3 to package.json and package-lock.json for improved routing capabilities.
- Updated TypeScript configuration in tsconfig.json to include additional file types and improve module resolution.

* feat: enhance listings and profiles management

- Updated the updateListing action to return specific moderation error responses instead of throwing exceptions.
- Added a new query to retrieve the current user's listings, including hidden and inactive items.
- Introduced a new query to fetch the current authenticated user's full profile, including non-public fields.
- Enhanced profile creation and update mutations to include additional fields and validation for email, year, and other profile attributes.
- Improved error handling in listing creation and editing to provide user-friendly feedback for moderation issues.
- Updated frontend components to reflect new profile and listing functionalities, including loading states and input validations.

* refactor: improve error messaging in profile creation tests

- Updated the test case for createProfile to clarify rejection conditions when no email is provided or available on identity.
- Enhanced error message to be more user-friendly, specifying that an email is required to create a profile.

* chore: update Node.js version in configuration files

* fix: ci script

* fix: ci checks

* fix: ci linux lightningcss binary

* fixed issue

* fixed issue

* potential redundent querires

* null request bug

* feat: implement push notifications and update dependencies

- Added support for push notifications using @convex-dev/expo-push-notifications.
- Updated the convex configuration to include push notification handling in message actions.
- Enhanced tests to verify push notification functionality upon message creation.
- Updated dependencies, including upgrading convex to version 1.32.0 and related Expo packages.
- Modified configuration files to reflect changes in app identifiers and notification settings.

* fix: improve push notification handling and enhance tests

- Updated the sendMessage action to use a more structured approach for push notifications based on the environment.
- Enhanced test setup for push notifications by refactoring the mock implementation for better clarity and maintainability.
- Added error handling for push notification failures during message delivery.
- Updated the useAuth hook to ensure push tokens are removed on sign-out, improving token management.

* fix: update app slug for consistency

* feat: add message seller button on listings

* feat: create inbox screen

* feat: create chat screen

* feat: implement read receipts for inbox in nav bar

* fix: remove errors when clicking inbox

* fix: prevent duplicate conversations

* feat: add read receipts to messages

* fix: show buyer name for listing creator

* fix: implemented code rabbit changes

* fix: add more code rabbit changes

* Development environment setup (#55)

* docs: add AGENTS.md with Cursor Cloud development instructions

* docs: update AGENTS.md with Convex cloud setup and login caveat

* docs: update AGENTS.md to reflect Convex cloud only (no self-hosted)

---------



* feat: UI overhaul (#57)

* feat: add saved listings functionality and enhance listings queries

- Implemented `savedListings` table with associated queries and mutations for saving, unsaving, and checking saved listings.
- Added `getListingsBySeller` query to fetch public active listings by seller.
- Updated `updateListingStatus` mutation to prevent changing status of sold listings.
- Enhanced tests for new saved listings features and updated existing tests for listing status changes.
- Improved UI components to support new functionalities and ensure better user experience.

* fix: agent bugs

* feat: integrate Sentry for error tracking and monitoring

- Added `@sentry/react-native` dependency to the project.
- Initialized Sentry in the application with the provided DSN for error tracking.
- Updated `app.json` to include Sentry configuration for Expo.
- Enhanced `_layout.tsx` to wrap the `RootLayout` component with Sentry for better error handling.

* refactor: update Metro configuration for Sentry integration

* feat: enhance authentication and profile management

- Introduced AppReviewOTP for Apple App Review email verification.
- Updated authentication flow to support specific email validation for the review process.
- Refactored profile bounds to a shared constants module for better maintainability.
- Improved error handling and user feedback in login and profile edit screens.
- Enhanced saved listings functionality to mark deleted and hidden listings as unavailable.
- Updated tests to cover new email validation logic and recent changes in saved listings.

* feat: add optional App Review OTP configuration

- Introduced optional App Review OTP functionality for Apple App Review login.
- Updated backend and frontend to support dynamic email and code configuration via environment variables.
- Enhanced email validation logic in the login process to accommodate the new OTP feature.
- Updated documentation to reflect the new configuration options for App Review.

* fix: icon and package mismatch (#59)

* refactor: unify authentication handling across backend modules (#60)

- Replaced direct calls to `getUserIdentity` with `requireAuthUserId` in various modules to ensure consistent user authentication checks.
- Updated ownership and user ID retrieval logic in listings, messages, profiles, and saved listings to utilize stable user IDs.
- Enhanced error handling for unauthorized actions across multiple mutations and queries.
- Removed legacy participant ID handling in messages and conversations to streamline user identification.
- Improved overall code maintainability and clarity by consolidating authentication logic.

* feat: enhance My Listings screen with status filtering and action han… (#61)

* feat: enhance My Listings screen with status filtering and action handling

- Added status filter options ('all', 'active', 'inactive', 'sold') to the My Listings screen for better listing management.
- Implemented action handling for editing, marking as sold, setting status, and deleting listings through a new action sheet.
- Improved error handling with user-friendly alerts for various actions.
- Updated UI components to reflect changes in listing management and enhance user experience.

* feat: unify authentication redirection across screens

- Implemented consistent use of `router.replace` for authentication redirection in various screens including Inbox, Home, My Listings, Search, Settings, and Listing Details.
- Enhanced user experience by replacing sign-in prompts with loading indicators while redirecting to the login page.
- Improved accessibility labels in ListingCard for better screen reader support.

* feat: implement messaging block checks and notification preferences (#62)

* feat: implement messaging block checks and notification preferences

- Added functionality to prevent users from messaging each other if they have blocked one another, enhancing user experience and safety.
- Introduced a new mutation to manage message notification preferences, allowing users to enable or disable notifications for incoming messages.
- Updated the user schema to include a field for message notification settings.
- Enhanced the login flow to prompt users for notification permissions, improving engagement with the messaging feature.
- Added tests to ensure proper functionality of blocking and notification features.

* chore: update package dependencies and app configuration

- Updated various Expo package versions in package-lock.json and package.json for improved stability and features.
- Added iOS icon path in app.json to enhance app branding on iOS devices.

* feat: enhance user blocking functionality and notification handling

- Implemented a new query to check if the current user is blocked by another user, improving user experience in conversations.
- Updated the blockUser mutation to ensure proper user ID normalization and error handling for blocking actions.
- Enhanced the Settings screen to provide better feedback during account deletion and sign-out processes.
- Refactored notification handling to ensure users are informed about permission status and potential issues when enabling notifications.
- Improved conversation detail screen to handle blocking states more effectively, preventing users from sending messages if blocked.

* feat: improve user blocking and notification preference handling

- Introduced a new function to validate peer user IDs in blocking mutations, enhancing error handling for user blocking actions.
- Updated blockUser and unblockUser mutations to utilize the new validation function, ensuring proper user ID normalization.
- Enhanced Settings screen to provide detailed alerts for notification preference updates, improving user feedback during push token management.
- Improved conversation detail screen to handle blocking states more effectively, preventing message sending if users are blocked.

* chore: update app configuration and package scripts

- Removed iOS icon path from app.json to streamline configuration.
- Updated package.json scripts for running Android and iOS to use 'expo run' commands, enhancing build process consistency.

* feat: transition to Convex Cloud and update environment configurations (#53)

* feat: transition to Convex Cloud and update environment configurations

- Updated backend configuration to use Convex Cloud instead of self-hosted setup.
- Revised environment variable instructions in documentation for backend and frontend.
- Enhanced the My Listings screen with delete functionality and improved user feedback.
- Updated various documentation files to reflect the migration to Convex Cloud and removed references to the legacy self-hosted setup.

* docs: update README and contributing guide for local development setup

- Added instructions for local setup without a Convex account in README.md.
- Revised contributing guide to clarify environment variable configuration options for both Convex Cloud and local development.
- Removed outdated Convex Cloud migration documentation and legacy self-hosted references.

* fix: preserve settings route typing after rebase

* fix: address Convex migration and settings review notes

---------



* Improve messaging safety and polish the web experience (#63)

* chore: update package dependencies and app configuration

- Updated various Expo package versions in package-lock.json and package.json for improved stability and features.
- Added iOS icon path in app.json to enhance app branding on iOS devices.

* chore: update app configuration and package scripts

- Removed iOS icon path from app.json to streamline configuration.
- Updated package.json scripts for running Android and iOS to use 'expo run' commands, enhancing build process consistency.

* feat: polish the Vercel web experience

Make the Expo web app feel intentional on desktop while steering app-only flows back to mobile.
Add Vercel-friendly config so preview and production builds resolve the right web origin.

* fix: remove duplicate layout import after rebase

* fix: clean up search list layout styles

* fix: preserve web search state

Keep the resolved app origin available at runtime and preserve active listing filters when web search updates or refreshes.

* chore: manual GitHub Action for dev → main release PR (#64)

* chore: update package dependencies and app configuration

- Updated various Expo package versions in package-lock.json and package.json for improved stability and features.
- Added iOS icon path in app.json to enhance app branding on iOS devices.

* chore: update app configuration and package scripts

- Removed iOS icon path from app.json to streamline configuration.
- Updated package.json scripts for running Android and iOS to use 'expo run' commands, enhancing build process consistency.

* feat: polish the Vercel web experience

Make the Expo web app feel intentional on desktop while steering app-only flows back to mobile.
Add Vercel-friendly config so preview and production builds resolve the right web origin.

* fix: remove duplicate layout import after rebase

* fix: clean up search list layout styles

* fix: preserve web search state

Keep the resolved app origin available at runtime and preserve active listing filters when web search updates or refreshes.

* chore: add manual GitHub Action to open dev→main release PR

- workflow_dispatch only; idempotent open PR or report existing
- document usage in contributing releases section

---------

Co-authored-by: dfed25 <domfederico21@gmail.com>
Co-authored-by: Jaydon Chen <79879038+jaydonkc@users.noreply.github.com>
Co-authored-by: Cole <hackman@calpoly.edu>
Co-authored-by: Haixin <haixinhuang502@gmail.com>
Co-authored-by: BoB121isawesome <79879038+BoB121isawesome@users.noreply.github.com>
Co-authored-by: lheutchy <lheutchy@gmail.com>
Co-authored-by: dfed25 <150391626+dfed25@users.noreply.github.com>
Co-authored-by: MatthewPhan <Matthewminhphan@gmail.com>
Co-authored-by: SamanSP1386 <saman.sepehr86@gmail.com>
Co-authored-by: Jaydon Bot <jaydon-bot@local>
Co-authored-by: Taye-Staats <tayestaats@outlook.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.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