feat: add support problem reports#147
Conversation
📝 WalkthroughWalkthroughThis pull request introduces a complete support report submission feature. Shared constants define report categories and numeric limits. A new Convex backend mutation validates, deduplicates, rate-limits, and persists support reports. The frontend adds a Settings entry point, dedicated report form with category selection and bounded description input, error mapping, and success feedback. ChangesSupport Report Submission Feature
Sequence DiagramsequenceDiagram
participant User as User (Native)
participant Settings as Account Settings
participant ReportScreen as Report Screen
participant Mutation as submitSupportReport
participant DB as supportReports DB
participant ErrorHandler as Error Handler
User->>Settings: Open account settings
Settings->>User: Display Support section + button
User->>ReportScreen: Tap "Report a Problem"
ReportScreen->>ReportScreen: Build reportContext (platform, app, OS, route)
User->>ReportScreen: Select category, enter description
User->>ReportScreen: Tap Submit
ReportScreen->>Mutation: Call submitSupportReport(category, description, context)
Mutation->>Mutation: Validate authentication
Mutation->>Mutation: Validate description (non-empty, ≤1200 chars)
Mutation->>DB: Query recent reports (last 10 min) for duplicates
alt Duplicate found
Mutation-->>ErrorHandler: "You already submitted this problem recently"
else Recent reports > 3 per 10 min
Mutation-->>ErrorHandler: "Support report limit reached. Please try again later"
else Daily limit exceeded
Mutation-->>ErrorHandler: "Support report limit reached. Please try again later"
else Validation passed
Mutation->>DB: Insert report with reporterId, category, description, context, createdAt
Mutation-->>ReportScreen: Return reportId
ReportScreen->>User: Show success card + "Done" button
end
User->>ReportScreen: Tap Done
ReportScreen->>Settings: Navigate back to Settings
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
backend/convex/supportReports.ts (1)
96-101: ⚡ Quick winConsider limiting query results for rate-limit checks.
The query fetches all recent reports to count them, but you only need to know if there are at least
SUPPORT_REPORTS_PER_TEN_MINUTESreports. Consider using.take(SUPPORT_REPORTS_PER_TEN_MINUTES + 1)to avoid fetching excessive rows if a user has many reports.⚡ Proposed optimization
const recentReports = await ctx.db .query('supportReports') .withIndex('by_reporter_createdAt', (q) => q.eq('reporterId', reporterId).gt('createdAt', recentCutoff) ) + .take(SUPPORT_REPORTS_PER_TEN_MINUTES + 1) .collect();Apply the same pattern to the daily limit query at lines 115-120:
const dailyReports = await ctx.db .query('supportReports') .withIndex('by_reporter_createdAt', (q) => q.eq('reporterId', reporterId).gt('createdAt', oneDayAgo) ) + .take(SUPPORT_REPORTS_PER_DAY + 1) .collect();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/convex/supportReports.ts` around lines 96 - 101, The rate-limit queries fetch all matching rows; limit them to only what you need by adding .take(...) to each query: in the recent reports query (the ctx.db.query('supportReports').withIndex('by_reporter_createdAt', ...) that assigns recentReports) append .take(SUPPORT_REPORTS_PER_TEN_MINUTES + 1) so you only fetch one more than the threshold, and do the same for the daily query that collects dailyReports by using .take(SUPPORT_REPORTS_PER_DAY + 1); this reduces work while preserving the ability to detect "over limit".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/app/report-problem.tsx`:
- Around line 62-70: The reportContext values (built in the useMemo assigned to
reportContext) currently forward sourceRoute, listingId, and conversationId raw;
clamp/sanitize these before submission by trimming and length-limiting
sourceRoute and converting listingId/conversationId to safe strings or numbers,
and remove any keys whose value is empty/undefined prior to invoking the report
mutation; update the useMemo to produce sanitized values (e.g., trim + substring
to a safe max length) and ensure the object passed into the mutation call (the
same reportContext used around the mutation invocation) has had empty fields
deleted so long/tampered deep links or falsy ids are omitted.
---
Nitpick comments:
In `@backend/convex/supportReports.ts`:
- Around line 96-101: The rate-limit queries fetch all matching rows; limit them
to only what you need by adding .take(...) to each query: in the recent reports
query (the ctx.db.query('supportReports').withIndex('by_reporter_createdAt',
...) that assigns recentReports) append .take(SUPPORT_REPORTS_PER_TEN_MINUTES +
1) so you only fetch one more than the threshold, and do the same for the daily
query that collects dailyReports by using .take(SUPPORT_REPORTS_PER_DAY + 1);
this reduces work while preserving the ability to detect "over limit".
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 74e2dabf-0af8-42f1-8eb1-db779546a08b
⛔ Files ignored due to path filters (2)
backend/convex/_generated/api.d.tsis excluded by!**/_generated/**package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (11)
backend/convex/__tests__/supportReports.test.tsbackend/convex/__tests__/testUtils.tsbackend/convex/schema.tsbackend/convex/supportReports.tsfrontend/app/_layout.tsxfrontend/app/account-settings.tsxfrontend/app/report-problem.tsxfrontend/lib/__tests__/user-flow-errors.test.tsfrontend/lib/user-flow-errors.tspackages/shared/constants/index.tspackages/shared/constants/support.ts
| const reportContext = useMemo( | ||
| () => ({ | ||
| platform: process.env.EXPO_OS ?? Platform.OS, | ||
| appVersion: Constants.expoConfig?.version ?? Constants.nativeApplicationVersion ?? undefined, | ||
| osVersion: Device.osVersion ?? undefined, | ||
| route: sourceRoute ?? '/report-problem', | ||
| listingId, | ||
| conversationId, | ||
| }), |
There was a problem hiding this comment.
Bound and sanitize context values before submit.
source, listingId, and conversationId are forwarded directly into context. A long/tampered deep link can exceed backend context limits and fail submission unnecessarily. Clamp these values client-side and omit empty fields before calling the mutation.
Suggested patch
-import { SUPPORT_REPORT_DESCRIPTION_MAX, type SupportReportCategory } from '`@polybuys/shared`';
+import {
+ SUPPORT_REPORT_CONTEXT_VALUE_MAX,
+ SUPPORT_REPORT_DESCRIPTION_MAX,
+ type SupportReportCategory,
+} from '`@polybuys/shared`';
@@
function firstParam(value: string | string[] | undefined) {
return Array.isArray(value) ? value[0] : value;
}
+
+function sanitizeContextValue(value: string | undefined) {
+ if (!value) return undefined;
+ const trimmed = value.trim();
+ if (!trimmed) return undefined;
+ return trimmed.slice(0, SUPPORT_REPORT_CONTEXT_VALUE_MAX);
+}
@@
- const reportContext = useMemo(
- () => ({
- platform: process.env.EXPO_OS ?? Platform.OS,
- appVersion: Constants.expoConfig?.version ?? Constants.nativeApplicationVersion ?? undefined,
- osVersion: Device.osVersion ?? undefined,
- route: sourceRoute ?? '/report-problem',
- listingId,
- conversationId,
- }),
- [conversationId, listingId, sourceRoute]
- );
+ const reportContext = useMemo(() => {
+ const context: Record<string, string> = {};
+ const platform = sanitizeContextValue(process.env.EXPO_OS ?? Platform.OS);
+ const appVersion = sanitizeContextValue(
+ Constants.expoConfig?.version ?? Constants.nativeApplicationVersion ?? undefined
+ );
+ const osVersion = sanitizeContextValue(Device.osVersion ?? undefined);
+ const route = sanitizeContextValue(sourceRoute ?? '/report-problem');
+ const sanitizedListingId = sanitizeContextValue(listingId);
+ const sanitizedConversationId = sanitizeContextValue(conversationId);
+
+ if (platform) context.platform = platform;
+ if (appVersion) context.appVersion = appVersion;
+ if (osVersion) context.osVersion = osVersion;
+ if (route) context.route = route;
+ if (sanitizedListingId) context.listingId = sanitizedListingId;
+ if (sanitizedConversationId) context.conversationId = sanitizedConversationId;
+
+ return context;
+ }, [conversationId, listingId, sourceRoute]);Also applies to: 95-99
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/app/report-problem.tsx` around lines 62 - 70, The reportContext
values (built in the useMemo assigned to reportContext) currently forward
sourceRoute, listingId, and conversationId raw; clamp/sanitize these before
submission by trimming and length-limiting sourceRoute and converting
listingId/conversationId to safe strings or numbers, and remove any keys whose
value is empty/undefined prior to invoking the report mutation; update the
useMemo to produce sanitized values (e.g., trim + substring to a safe max
length) and ensure the object passed into the mutation call (the same
reportContext used around the mutation invocation) has had empty fields deleted
so long/tampered deep links or falsy ids are omitted.
Linked Issues
Closes: N/A
Linear: POLY-100
Summary
Adds a signed-in Report a Problem flow from Account Settings for app bugs and support issues, separate from existing content moderation reports.
Support submissions are stored in a new Convex
supportReportstable with authenticated user context, app/device metadata, duplicate rapid-submission blocking, and rate limiting.How to Test
npm run typechecknpm test -- --runInBandnpm run dev:backendnpm run devChecklist
devScreenshots / Demos
Add screenshots of Settings support entry and Report a Problem form.
Summary by CodeRabbit