0.10.0#156
Conversation
…ore and pcListings modules
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the ✨ Finishing Touches🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
|
Claude finished @Producdevity's task —— View job PR Review Complete ✅I've reviewed this v0.10.0 PR and here's my comprehensive feedback: Todo List:
Overall AssessmentThis PR shows excellent code quality with significant improvements in error handling, validation, and code organization. The changes demonstrate mature software engineering practices. Strengths 🌟1. Error Handling Architecture (
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (11)
src/app/listings/components/shared/CustomFieldRenderer.tsx (1)
55-61: Required boolean fields will be treated as invalid when false.
requiredis set to a message for all required fields, but boolean controls typically submitfalselegitimately. In RHF,requiredon a checkbox forces “truthy,” contradicting your “Boolean fields are always valid” comment. Mark booleans as not required and keep your custom validate.function getValidationRules(): ValidationRules { return { - required: props.fieldDef.isRequired ? `${props.fieldDef.label} is required` : false, + required: + props.fieldDef.type === CustomFieldType.BOOLEAN + ? false + : props.fieldDef.isRequired + ? `${props.fieldDef.label} is required` + : false, validate: props.fieldDef.isRequired ? (value: unknown) => { // Boolean fields are always valid if (props.fieldDef.type === CustomFieldType.BOOLEAN) return truesrc/lib/errors.ts (1)
219-225: Copy/paste bug in device.alreadyExists messageUser-facing string includes a stray “Permission with this key already exists” prefix. Fix to avoid confusing error text.
Apply this diff:
static device = { notFound: () => AppError.notFound('Device'), alreadyExists: (modelName: string) => - AppError.conflict( - `Permission with this key already exists A device with model name "${modelName}" already exists for this brand`, - ), + AppError.conflict( + `A device with model name "${modelName}" already exists for this brand`, + ), inUse: (count: number) => AppError.resourceInUse('device', count), }docs/MOBILE_API.md (1)
257-262: Wrong description for listings.getEmulatorConfigCurrently says “Delete a comment”. Should reflect the emulator configuration fetch used by mobile listing flows.
Apply this diff:
#### 34. **getEmulatorConfig** - **Method**: GET - **Path**: `/listings.getEmulatorConfig` -- **Description**: Delete a comment +- **Description**: Get emulator configuration for listing creation/edit - **Tags**: listingspublic/api-docs/mobile-openapi.json (1)
7967-7969: Copy/paste error: getEmulatorConfig summary/description says “Delete a comment”This endpoint retrieves emulator config but the summary/description say “Delete a comment”. Fix to avoid misleading API consumers.
Apply this diff:
- "summary": "Delete a comment", - "description": "Delete a comment", + "summary": "Get emulator config for a listing", + "description": "Get emulator configuration payload for the given listing/emulator type",src/server/api/routers/pcListings.ts (7)
413-445: Critical: returned listing may not reflect newly saved customFieldValues (and lacks atomicity).You update the listing (include current customFieldValues), then deleteMany and createMany new values, and finally return the pre-mutation updatedPcListing. Callers won’t see the new custom fields. Also, delete/create should be atomic.
Wrap in a transaction and re-fetch after writes:
- const updatedPcListing = await ctx.prisma.pcListing.update({ - where: { id }, - data: { ...updateData, updatedAt: new Date() }, - include: { - game: { include: { system: true } }, - cpu: { include: { brand: true } }, - gpu: { include: { brand: true } }, - emulator: true, - performance: true, - author: true, - customFieldValues: { include: { customFieldDefinition: true } }, - }, - }) - - // Handle custom field values if provided - if (customFieldValues) { - // Delete existing custom field values - await ctx.prisma.pcListingCustomFieldValue.deleteMany({ where: { pcListingId: id } }) - - // Create new custom field values - if (customFieldValues.length > 0) { - await ctx.prisma.pcListingCustomFieldValue.createMany({ - data: customFieldValues.map((cfv) => ({ - pcListingId: id, - customFieldDefinitionId: cfv.customFieldDefinitionId, - value: cfv.value, - })), - }) - } - } - - return updatedPcListing + const updatedPcListing = await ctx.prisma.$transaction(async (tx) => { + // 1) Update core fields + await tx.pcListing.update({ + where: { id }, + data: { ...updateData, updatedAt: new Date() }, + }) + + // 2) Replace custom field values if provided + if (customFieldValues) { + await tx.pcListingCustomFieldValue.deleteMany({ where: { pcListingId: id } }) + if (customFieldValues.length > 0) { + await tx.pcListingCustomFieldValue.createMany({ + data: customFieldValues.map((cfv) => ({ + pcListingId: id, + customFieldDefinitionId: cfv.customFieldDefinitionId, + value: cfv.value, + })), + }) + } + } + + // 3) Return fresh view with includes + return tx.pcListing.findUniqueOrThrow({ + where: { id }, + include: { + game: { include: { system: true } }, + cpu: { include: { brand: true } }, + gpu: { include: { brand: true } }, + emulator: true, + performance: true, + author: true, + customFieldValues: { include: { customFieldDefinition: true } }, + }, + }) + }) + + return updatedPcListingAlso applies to: 430-441
725-751: Same issue in updateAdmin: response may exclude the newly written customFieldValues. Make atomic.The admin path updates the listing, then mutates custom fields, and returns the pre-mutation entity. Mirror the transaction-and-refetch pattern.
- const updatedPcListing = await ctx.prisma.pcListing.update({ - where: { id }, - data: { ...data, updatedAt: new Date() }, - include: pcListingDetailInclude, - }) - - // Handle custom field values if provided - if (customFieldValues) { - // Delete existing custom field values - await ctx.prisma.pcListingCustomFieldValue.deleteMany({ - where: { pcListingId: id }, - }) - - // Create new custom field values - if (customFieldValues.length > 0) { - await ctx.prisma.pcListingCustomFieldValue.createMany({ - data: customFieldValues.map((cfv) => ({ - pcListingId: id, - customFieldDefinitionId: cfv.customFieldDefinitionId, - value: cfv.value, - })), - }) - } - } - - return updatedPcListing + const updatedPcListing = await ctx.prisma.$transaction(async (tx) => { + await tx.pcListing.update({ + where: { id }, + data: { ...data, updatedAt: new Date() }, + }) + + if (customFieldValues) { + await tx.pcListingCustomFieldValue.deleteMany({ where: { pcListingId: id } }) + if (customFieldValues.length > 0) { + await tx.pcListingCustomFieldValue.createMany({ + data: customFieldValues.map((cfv) => ({ + pcListingId: id, + customFieldDefinitionId: cfv.customFieldDefinitionId, + value: cfv.value, + })), + }) + } + } + + return tx.pcListing.findUniqueOrThrow({ + where: { id }, + include: pcListingDetailInclude, + }) + }) + + return updatedPcListing
1118-1121: Bug: missing throw/return on forbidden comment edit.The code calls AppError.forbidden(...) but does not throw or return, so unauthorized users will still edit the comment.
Apply this diff:
- if (!canEdit) { - AppError.forbidden('You do not have permission to edit this comment') - } + if (!canEdit) { + throw AppError.forbidden('You do not have permission to edit this comment') + }
1154-1156: Bug: missing throw/return on forbidden comment delete.Same issue as updateComment; execution proceeds without stopping.
- if (!canDelete) { - AppError.forbidden('You do not have permission to delete this comment') - } + if (!canDelete) { + throw AppError.forbidden('You do not have permission to delete this comment') + }
1409-1411: Bug: verification path does not return/throw when listing is missing.Currently it evaluates ResourceError.pcListing.notFound() and continues, allowing a verification to be created for a non-existent listing.
- if (!pcListing) { - ResourceError.pcListing.notFound() - } + if (!pcListing) { + throw ResourceError.pcListing.notFound() + }
1237-1254: Bug: report creation path lacks throws for bad requests.Both “cannot report own listing” and “already reported” should stop execution.
- if (pcListing.authorId === userId) { - AppError.badRequest('You cannot report your own listing') - } + if (pcListing.authorId === userId) { + throw AppError.badRequest('You cannot report your own listing') + } ... - if (existingReport) { - AppError.badRequest('You have already reported this listing') - } + if (existingReport) { + throw AppError.badRequest('You have already reported this listing') + }
329-339: Align AppError/ResourceError Handling: Always ThrowThe grep output confirms mixed use of
returnandthrowforAppError/ResourceErroracross thepcListingsrouter (dozens of occurrences spanning lines 103–1448). To ensure tRPC clients receive proper exceptions (not serialized data), pick one approach—always throw—and apply it consistently.Next steps:
- Decide on a single error semantics: use
throwfor allAppErrorandResourceErrorcases in this router.- Replace every
withreturn ResourceError.…() return AppError.…()throw ResourceError.…() throw AppError.…()- Consult the grep output for all affected locations (e.g., lines 104, 213, 220–221, 239–240, 267–269, 333–335, 407–409, 453–455, 501–503, 507–510, 521–523, 542–544, 548–553, 562–564, 589–591, 618–620, 722–724, 776–778, 796–798, 832–838, 849–851, 880–886, 902–904, 1058–1060, 1066–1068, 1112–1115, 1144–1147, 1174–1176, 1233–1235, 1325–1327, 1446–1448).
- Example refactor:
// before if (!pcListing) return ResourceError.pcListing.notFound() // after if (!pcListing) throw ResourceError.pcListing.notFound()- After making changes, re-run the provided grep script to verify no remaining
return AppError/return ResourceErrorusages.
🧹 Nitpick comments (25)
package.json (1)
3-3: Automate CACHE_NAME sync in the npm version lifecycleVerified that both
public/service-worker.jsandpublic/sw-register.jshave theirCACHE_NAMEset toemuready_v0.10.0, matching the"version": "0.10.0"inpackage.json. ✅To eliminate manual edits on each release, hook your existing
version:syncscript intonpm version:• File:
package.json"scripts": { + "version": "npm run version:sync && git add -A", "version:sync": "node scripts/sync-version.js" }This way a single
npm version <new>will update your package version, rewrite both cache-name constants, and stage everything for commit in one go.public/service-worker.js (1)
132-134: Docstring nit: clarify the policy is stale‑while‑revalidate with TTL gating.The handler still serves stale cache immediately if present and revalidates in the background (SWr). The current comment “to prevent stale data” can mislead.
- * Implements cache policies based on resource type to prevent stale data. + * Implements stale-while-revalidate caching with TTL gating per resource type: + * serve cached if present, revalidate in background, and stamp freshness.public/sw-register.js (1)
1-1: CACHE_NAME bump mirrors the service worker.Good consistency with public/service-worker.js. Minor note: since the SW’s activate event already deletes old caches, this page-load cleanup is redundant but harmless. Keep if you want belt-and-suspenders behavior; otherwise you can drop the window-level cleanup to reduce work on slow devices.
src/app/listings/components/shared/CustomFieldRenderer.tsx (1)
55-73: Optional: extract the emptiness check into a shared helper for consistency.
FormValidationSummaryand custom field components likely need the same notion of “empty.” Centralizing this reduces drift and future bugs.I can extract
isValueEmptyintoformHelpers.ts(or a newvalidators.ts) and update call sites. Want me to push that refactor?RELEASE_NOTES.md (1)
1-12: Great release notes; minor markdownlint: avoid using bold as headings (MD036).Several sections use
**...**where###headings would satisfy linters and improve structure. Consider converting emphasized lines like “IGDB Integration for Game Search (BETA)” to proper subheadings.Example edit:
-**🔍 IGDB Integration for Game Search (BETA)** +### 🔍 IGDB Integration for Game Search (BETA)lint-staged.config.mjs (1)
7-9: Nit: Fix filename in the header comment to match .mjsThe JSDoc header says lint-staged.config.js, but the file is .mjs. Align to avoid confusion in future searches and reviews.
Apply this diff:
- * @filename: lint-staged.config.js + * @filename: lint-staged.config.mjssrc/lib/errors.ts (1)
136-141: Type consistency: make AppError.captcha return typeneverAll other throw helpers are typed as never; captcha should match for better type-narrowing downstream.
Apply this diff:
- static captcha(message?: string) { + static captcha(message?: string): never { throw new TRPCError({ code: ERROR_CODES.BAD_REQUEST, message: message ? `CAPTCHA verification failed: ${message}` : ERROR_MESSAGES.INVALID_CAPTCHA, }) }docs/MOBILE_API.md (1)
1-9: Optional: markdownlint MD036 (“emphasis used instead of heading”)The auto-generated timestamp line uses italics; some linters prefer a heading. Optional change if you want a cleaner lint pass for docs.
Apply this diff:
-*Auto-generated on: 2025-08-25T04:27:54.741Z* +### Auto-generated on: 2025-08-25T04:27:54.741Zpublic/api-docs/mobile-openapi.json (4)
935-958: Prevent duplicate IDs in GetUserVotesSchemaConsider enforcing unique IDs to avoid redundant lookups on the server.
Apply this diff:
"commentIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "minItems": 1, - "maxItems": 100 + "maxItems": 100, + "uniqueItems": true }
959-991: Align examples/validation for ReportCommentSchemaSchema looks good. Two improvements:
- Clients may send empty strings for optional description; if provided, enforce min length 1.
- Add description to enum for clarity.
Apply this diff:
"reason": { "type": "string", "enum": [ "INAPPROPRIATE_CONTENT", "SPAM", "MISLEADING_INFORMATION", "OTHER" - ] + ], + "description": "Reason for reporting the comment" }, "description": { "type": "string", - "maxLength": 1000 + "maxLength": 1000, + "minLength": 1 }
5-5: Global note: auth status text conflicts with security configurationThe intro says Clerk-protected routes “isn't implemented yet”, but many paths now declare
security: [{"ClerkAuth": []}]and a global security requirement exists. Update the note to reflect current reality or remove it.Would you like me to propose an updated intro paragraph that matches the current auth model?
909-991: Optional: reduce schema duplication and improve OAS compatibilityMultiple schemas embed a local
definitionsobject and then$refinto it. This pattern is non-idiomatic in OpenAPI 3.0 and can confuse tooling. Prefer defining the schema object inline undercomponents.schemas(or$refto anothercomponents.schemas.*), and avoidtypearrays in favor ofnullable: true.If helpful, I can open a follow-up PR to:
- Flatten VoteCommentSchema/GetUserVotesSchema/ReportCommentSchema into
components.schemaswithout nesteddefinitions.- Run an OpenAPI linter (Spectral) pass and fix OAS 3.0 compliance warnings.
Also applies to: 8109-8558
src/app/listings/[id]/components/EditListingModal.tsx (2)
65-75: Guard against undefined arrays when initializing custom fields.If emulator.customFieldDefinitions or customFieldValues is ever undefined/null, initializeCustomFieldValues will throw. The server route likely includes definitions, but this guard costs nothing and prevents a hard crash when data contracts drift.
Apply this diff:
- const defaultCustomFieldValues = initializeCustomFieldValues( - listingQuery.data.customFieldValues, - listingQuery.data.emulator.customFieldDefinitions, - ) + const defaultCustomFieldValues = initializeCustomFieldValues( + listingQuery.data.customFieldValues ?? [], + listingQuery.data.emulator.customFieldDefinitions ?? [], + )
191-219: Optional: reuse the shared PerformanceSelector for parity with PC modal.This manual SelectInput is fine, but using the shared PerformanceSelector (as in EditPcListingModal) would reduce divergence and ensure consistent error/render behavior.
If you want, I can draft the small refactor.
src/app/pc-listings/[id]/components/EditPcListingModal.tsx (2)
76-90: Harden initializeCustomFieldValues call.Add nullish guards to avoid crashes if the server ever omits customFieldDefinitions or customFieldValues.
- const defaultCustomFieldValues = initializeCustomFieldValues( - pcListingQuery.data.customFieldValues, - pcListingQuery.data.emulator.customFieldDefinitions, - ) + const defaultCustomFieldValues = initializeCustomFieldValues( + pcListingQuery.data.customFieldValues ?? [], + pcListingQuery.data.emulator.customFieldDefinitions ?? [], + )
50-52: Consolidate to a Single Performance-Scales EndpointBoth
api.listings.performanceScales(in the listings router) andapi.performanceScales.get(in the performanceScales router) exist and currently return the same data—an ascending list of scales—but via two different handlers. Maintaining both invites drift and extra upkeep. I recommend picking one (ideally the more flexibleapi.performanceScales.get) and using it everywhere.• Replace all calls to
api.listings.performanceScales.useQuery(...)
with
api.performanceScales.get.useQuery(...)
in these files:
– src/app/pc-listings/[id]/components/EditPcListingModal.tsx (lines ~50–52)
– src/app/pc-listings/PcListingsPage.tsx
– src/app/v2/listings/V2ListingsPage.tsx
– src/app/listings/ListingsPage.tsx
– src/app/listings/new/NewListingPage.tsx
– src/app/listings/[id]/components/EditListingModal.tsx
– …and any other usages ofapi.listings.performanceScalesshown by your ripgrep search.• After migrating, remove the
performanceScalesfield from the listings router in
src/server/api/routers/listings/core.tsto avoid duplication.This will keep your API surface clean and reduce future maintenance risk.
src/app/listings/components/shared/FormValidationSummary.tsx (1)
45-58: Tighten fallback detection for custom-field errors without messages.The current fallback checks any truthy fieldError.value, which is usually fine, but can also trip when value exists for non-required errors in some RHF shapes. Prefer checking for an error-like object shape.
- const customFieldErrors = props.errors.customFieldValues - if (customFieldErrors && Array.isArray(customFieldErrors)) { - const hasEmptyRequiredFields = customFieldErrors.some((fieldError) => fieldError?.value) - if (hasEmptyRequiredFields) errorMessages.push('Please fill in all required custom fields') - } + const customFieldErrors = props.errors.customFieldValues + if (Array.isArray(customFieldErrors)) { + const hasCustomFieldIssues = customFieldErrors.some((fe) => { + const v = (fe as any)?.value + return v && typeof v === 'object' && ('type' in v || 'ref' in v || 'message' in v) + }) + if (hasCustomFieldIssues) errorMessages.push('Please fill in all required custom fields') + }src/app/listings/components/shared/utils/formHelpers.ts (1)
26-40: Optional: default empty arrays for resilience and DX.Allow callers to omit undefined by giving parameters default values.
-export function initializeCustomFieldValues( - existingValues: CustomFieldValue[], - fieldDefinitions: CustomFieldDefinition[], -): { customFieldDefinitionId: string; value: unknown }[] { +export function initializeCustomFieldValues( + existingValues: CustomFieldValue[] = [], + fieldDefinitions: CustomFieldDefinition[] = [], +): { customFieldDefinitionId: string; value: unknown }[] {src/server/api/routers/pcListings.ts (1)
949-963: Nit: event type constant is identical for upvote/downvote.Both branches emit NOTIFICATION_EVENTS.LISTING_VOTED. If you intended different events for like/dislike, branch on value. Otherwise, consider inlining the constant.
- notificationEventEmitter.emitNotificationEvent({ - eventType: value ? NOTIFICATION_EVENTS.LISTING_VOTED : NOTIFICATION_EVENTS.LISTING_VOTED, + notificationEventEmitter.emitNotificationEvent({ + eventType: NOTIFICATION_EVENTS.LISTING_VOTED,src/server/api/routers/listings/core.ts (6)
723-725: Use a consistent “owner” truth value across the methodAfter the refactor above, keep a single
const isOwner = ...declaration and reuse it. Multiple derivations of ownership increase drift risk.- const isOwner = listing.authorId === ctx.session.user.id + const isOwner = listing.authorId === ctx.session.user.id(No functional change; ensure only one declaration remains after the moderator-path refactor.)
807-815: Unify edit-time limit math to the shared constantYou define
EDIT_TIME_LIMITalready; re-deriving milliseconds here is error-prone.- const timeLimit = EDIT_TIME_LIMIT_MINUTES * 60 * 1000 + const timeLimit = EDIT_TIME_LIMIT
821-852: Consider post-update cache invalidation to reflect edits immediatelyCreate flow invalidates list pages, sitemap, and tags when status is APPROVED. Edits (notes/performance/custom fields) won’t propagate until TTL unless you revalidate.
Example (outside the transaction, after it resolves):
const updated = await ctx.prisma.$transaction(/* ...existing code... */) // Optional: only when visible entities may be affected if (listing.status === ApprovalStatus.APPROVED) { await invalidateListPages() await invalidateSitemap() await revalidateByTag('listings') await revalidateByTag(`game-${updated.gameId}`) await revalidateByTag(`device-${updated.deviceId}`) await revalidateByTag(`emulator-${updated.emulatorId}`) } return updatedIf you want this, I can open a small follow-up PR wiring the IDs you need for tag invalidation.
565-575: Standardize error surface in vote(): mix of ResourceError and AppErrorThis branch returns
ResourceError.listing.notFound(), but the “toggle/remove” branch above returnsAppError.notFound('Listing')(Line 523). Mixing shapes complicates client handling.Apply this diff to the earlier branch for consistency:
- if (!listingForRemoval) return AppError.notFound('Listing') + if (!listingForRemoval) return ResourceError.listing.notFound()If the intended convention is “always ResourceError for listing flows,” consider aligning the other “not found”s similarly in this file.
854-856: Align error propagation style in getForUserEdit (throw vs return)This procedure “returns”
ResourceError.listing.canOnlyEditOwn()on authorization failure but “throws” for not-found. Pick one style for predictable client contracts (the rest of this file mostly “returns”ResourceError.*).Apply this diff:
- if (!listing) throw ResourceError.listing.notFound() + if (!listing) return ResourceError.listing.notFound()Optionally, convert the
canOnlyEditOwn()branch tothrowinstead—just be consistent across procedures.Also applies to: 882-890
705-780: Minor consistency: prefer a single permission helperYou use both
roleIncludesRoleandhasPermissionin this file to test moderator capabilities. Prefer one (the newerhasPermission?), to avoid drift.If we standardize on
hasPermission, I can sweep the file in a follow-up PR.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (17)
RELEASE_NOTES.md(1 hunks)docs/MOBILE_API.md(12 hunks)lint-staged.config.mjs(1 hunks)package.json(1 hunks)prisma/migrations/20250825_add_missing_trust_actions/migration.sql(1 hunks)public/api-docs/mobile-openapi.json(3 hunks)public/service-worker.js(1 hunks)public/sw-register.js(1 hunks)src/app/listings/[id]/components/EditListingModal.tsx(2 hunks)src/app/listings/components/shared/CustomFieldRenderer.tsx(2 hunks)src/app/listings/components/shared/FormValidationSummary.tsx(1 hunks)src/app/listings/components/shared/custom-fields/CustomFieldTypeDriverVersion.tsx(0 hunks)src/app/listings/components/shared/utils/formHelpers.ts(1 hunks)src/app/pc-listings/[id]/components/EditPcListingModal.tsx(2 hunks)src/lib/errors.ts(2 hunks)src/server/api/routers/listings/core.ts(12 hunks)src/server/api/routers/pcListings.ts(3 hunks)
💤 Files with no reviewable changes (1)
- src/app/listings/components/shared/custom-fields/CustomFieldTypeDriverVersion.tsx
🧰 Additional context used
🪛 Biome (2.1.2)
src/server/api/routers/pcListings.ts
[error] 386-386: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 387-387: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 388-388: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🪛 markdownlint-cli2 (0.17.2)
RELEASE_NOTES.md
5-5: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
15-15: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
24-24: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
33-33: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
40-40: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
49-49: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
56-56: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
65-65: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
73-73: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
82-82: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
88-88: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
96-96: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
102-102: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
110-110: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
117-117: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
docs/MOBILE_API.md
3-3: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
🪛 LanguageTool
docs/MOBILE_API.md
[grammar] ~5-~5: There might be a mistake here.
Context: ...n: 2025-08-25T04:27:54.741Z* ## Summary - Total Endpoints: 81 - **Public Endpoin...
(QB_NEW_EN)
[grammar] ~6-~6: There might be a mistake here.
Context: ...Z* ## Summary - Total Endpoints: 81 - Public Endpoints: 49 - **Protected End...
(QB_NEW_EN)
[grammar] ~7-~7: There might be a mistake here.
Context: ...dpoints**: 81 - Public Endpoints: 49 - Protected Endpoints: 32 - **OpenAPI Ve...
(QB_NEW_EN)
[grammar] ~8-~8: There might be a mistake here.
Context: ...ints**: 49 - Protected Endpoints: 32 - OpenAPI Version: 3.0.0 ## Base URL `/...
(QB_NEW_EN)
[grammar] ~134-~134: There might be a mistake here.
Context: ...ameById` - Description: Search games - Tags: games #### 17. **findSwitchTit...
(QB_NEW_EN)
[grammar] ~486-~486: There might be a mistake here.
Context: ...r token required #### 14. voteComment - Method: POST - Path: `/listings.vo...
(QB_NEW_EN)
[grammar] ~487-~487: There might be a mistake here.
Context: ...# 14. voteComment - Method: POST - Path: /listings.voteComment - **Desc...
(QB_NEW_EN)
[grammar] ~488-~488: There might be a mistake here.
Context: ...omment** - Method: POST - Path: /listings.voteComment - Description: Vote on a comment - **Tag...
(QB_NEW_EN)
[grammar] ~489-~489: There might be a mistake here.
Context: ...nt` - Description: Vote on a comment - Tags: listings - Request Body: JSO...
(QB_NEW_EN)
[grammar] ~490-~490: There might be a mistake here.
Context: ...: Vote on a comment - Tags: listings - Request Body: JSON object required - *...
(QB_NEW_EN)
[grammar] ~491-~491: There might be a mistake here.
Context: ...- Request Body: JSON object required - Content-Type: application/json - **Aut...
(QB_NEW_EN)
[grammar] ~492-~492: There might be a mistake here.
Context: ...red - Content-Type: application/json - Authentication: Bearer token required ...
(QB_NEW_EN)
[grammar] ~495-~495: There might be a mistake here.
Context: ...required #### 15. getUserCommentVotes - Method: GET - Path: `/listings.get...
(QB_NEW_EN)
[grammar] ~496-~496: There might be a mistake here.
Context: ...getUserCommentVotes* - Method: GET - Path: /listings.getUserCommentVotes ...
(QB_NEW_EN)
[grammar] ~497-~497: There might be a mistake here.
Context: ...ntVotes** - Method: GET - Path: /listings.getUserCommentVotes - Description: Get user votes for multip...
(QB_NEW_EN)
[grammar] ~498-~498: There might be a mistake here.
Context: ...**: Get user votes for multiple comments - Tags: listings - Authentication: ...
(QB_NEW_EN)
[grammar] ~504-~504: There might be a mistake here.
Context: ...16. reportComment - Method: POST - Path: /listings.reportComment - **De...
(QB_NEW_EN)
[grammar] ~505-~505: There might be a mistake here.
Context: ...omment** - Method: POST - Path: /listings.reportComment - Description: Report a comment - **Tags...
(QB_NEW_EN)
[grammar] ~506-~506: There might be a mistake here.
Context: ...ent` - Description: Report a comment - Tags: listings - Request Body: JSO...
(QB_NEW_EN)
[grammar] ~507-~507: There might be a mistake here.
Context: ...*: Report a comment - Tags: listings - Request Body: JSON object required - *...
(QB_NEW_EN)
[grammar] ~508-~508: There might be a mistake here.
Context: ...- Request Body: JSON object required - Content-Type: application/json - **Aut...
(QB_NEW_EN)
[grammar] ~509-~509: There might be a mistake here.
Context: ...red - Content-Type: application/json - Authentication: Bearer token required ...
(QB_NEW_EN)
[grammar] ~512-~512: There might be a mistake here.
Context: ...*: Bearer token required #### 17. get - Method: GET - Path: `/notification...
(QB_NEW_EN)
[grammar] ~513-~513: There might be a mistake here.
Context: ...ired #### 17. get - Method: GET - Path: /notifications.get - **Descrip...
(QB_NEW_EN)
[grammar] ~514-~514: There might be a mistake here.
Context: .... get - Method: GET - Path: /notifications.get - Description: Get notifications with pa...
(QB_NEW_EN)
[grammar] ~515-~515: There might be a mistake here.
Context: ...ion**: Get notifications with pagination - Tags: notifications - **Authenticatio...
(QB_NEW_EN)
[grammar] ~520-~520: There might be a mistake here.
Context: ...r token required #### 18. unreadCount - Method: GET - Path: `/notification...
(QB_NEW_EN)
[grammar] ~521-~521: There might be a mistake here.
Context: ...## 18. unreadCount - Method: GET - Path: /notifications.unreadCount - *...
(QB_NEW_EN)
[grammar] ~522-~522: There might be a mistake here.
Context: ...adCount** - Method: GET - Path: /notifications.unreadCount - Description: Get unread notification c...
(QB_NEW_EN)
[grammar] ~523-~523: There might be a mistake here.
Context: ...ription**: Get unread notification count - Tags: notifications - **Authenticatio...
(QB_NEW_EN)
[grammar] ~528-~528: There might be a mistake here.
Context: ...er token required #### 19. markAsRead - Method: POST - Path: `/notificatio...
(QB_NEW_EN)
[grammar] ~537-~537: There might be a mistake here.
Context: ...token required #### 20. markAllAsRead - Method: POST - Path: `/notificatio...
(QB_NEW_EN)
[grammar] ~538-~538: There might be a mistake here.
Context: ...20. markAllAsRead - Method: POST - Path: /notifications.markAllAsRead -...
(QB_NEW_EN)
[grammar] ~539-~539: There might be a mistake here.
Context: ...AsRead** - Method: POST - Path: /notifications.markAllAsRead - Description: Mark all notifications as...
(QB_NEW_EN)
[grammar] ~540-~540: There might be a mistake here.
Context: ...iption**: Mark all notifications as read - Tags: notifications - **Authenticatio...
(QB_NEW_EN)
[grammar] ~563-~563: There might be a mistake here.
Context: ...*: Bearer token required #### 23. get - Method: GET - Path: `/preferences....
(QB_NEW_EN)
[grammar] ~564-~564: There might be a mistake here.
Context: ...ired #### 23. get - Method: GET - Path: /preferences.get - **Descripti...
(QB_NEW_EN)
[grammar] ~565-~565: There might be a mistake here.
Context: .... get - Method: GET - Path: /preferences.get - Description: Get user preferences - **...
(QB_NEW_EN)
[grammar] ~566-~566: There might be a mistake here.
Context: ... - Description: Get user preferences - Tags: preferences - *Authentication...
(QB_NEW_EN)
[grammar] ~580-~580: There might be a mistake here.
Context: ...rer token required #### 25. addDevice - Method: POST - Path: `/preferences...
(QB_NEW_EN)
[grammar] ~598-~598: There might be a mistake here.
Context: ...n required #### 27. bulkUpdateDevices - Method: POST - Path: `/preferences...
(QB_NEW_EN)
[grammar] ~607-~607: There might be a mistake here.
Context: ...oken required #### 28. bulkUpdateSocs - Method: POST - Path: `/preferences...
(QB_NEW_EN)
[grammar] ~616-~616: There might be a mistake here.
Context: ...oken required #### 29. currentProfile - Method: GET - Path: `/preferences....
(QB_NEW_EN)
[grammar] ~617-~617: There might be a mistake here.
Context: ...29. currentProfile - Method: GET - Path: /preferences.currentProfile - ...
(QB_NEW_EN)
[grammar] ~618-~618: There might be a mistake here.
Context: ...Profile** - Method: GET - Path: /preferences.currentProfile - Description: Get current user's profil...
(QB_NEW_EN)
[grammar] ~619-~619: There might be a mistake here.
Context: ...escription**: Get current user's profile - Tags: preferences - *Authentication...
(QB_NEW_EN)
[grammar] ~625-~625: There might be a mistake here.
Context: ... #### 30. profile - Method: GET - Path: /preferences.profile - **Descr...
(QB_NEW_EN)
[grammar] ~626-~626: There might be a mistake here.
Context: ...profile** - Method: GET - Path: /preferences.profile - Description: Get user profile by ID - ...
(QB_NEW_EN)
[grammar] ~627-~627: There might be a mistake here.
Context: ... Description: Get user profile by ID - Tags: preferences - *Authentication...
(QB_NEW_EN)
[grammar] ~632-~632: There might be a mistake here.
Context: ...token required #### 31. updateProfile - Method: POST - Path: `/preferences...
(QB_NEW_EN)
[grammar] ~641-~641: There might be a mistake here.
Context: ...Bearer token required #### 32. myInfo - Method: GET - Path: `/trust.myInfo...
(QB_NEW_EN)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: e2e-tests
🔇 Additional comments (22)
public/service-worker.js (1)
42-42: CACHE_NAME bump is consistent with the release.Matches the 0.10.0 version and the sw-register.js change. No functional concerns here.
RELEASE_NOTES.md (1)
40-46: Claimed SW improvements align with the code.The “Service Worker Enhancements” (smart cache durations, auto-cleanup, offline support) match the code changes in public/service-worker.js and public/sw-register.js for v0.10.0. 👍
lint-staged.config.mjs (1)
11-14: LGTM: sw-register.js now staged alongside service-worker.jsAdding public/sw-register.js to the git add step prevents version drift between the SW and the registrar after sync-version runs. Looks good.
prisma/migrations/20250825_add_missing_trust_actions/migration.sql (2)
4-11: Idempotent enum additions look goodUsing ADD VALUE IF NOT EXISTS for each new trust_action is safe to run across environments and supports repeated deploys. Naming is consistent with existing enum style.
1-3: Ensure this migration runs outside a transaction• Add a file-level directive at the very top of
prisma/migrations/20250825_add_missing_trust_actions/migration.sqlto prevent Prisma Migrate from wrapping the statements in a transaction:/* MIGRATION_DISABLE_TRANSACTION */ -- Add missing trust action enum values that were added via db push but never migrated -- These values already exist in dev/staging but are missing from production ALTER TYPE trust_action ADD VALUE 'NEW_ACTION'; ALTER TYPE trust_action ADD VALUE 'ANOTHER_ACTION';• PostgreSQL versions before 12 do not allow
ALTER TYPE … ADD VALUEinside any transaction block; running these statements outside a transaction is required to avoid errors (postgresql.org).
• Even in PostgreSQL 12 and later, while you can add enum values inside a transaction, you cannot use the new value until the transaction commits; splitting into a separate, non-transactional migration avoids subtle “unsafe use of new enum value” failures (lightrun.com).
• Prisma’s official migration docs and “limitations and known issues” guide do not document a built-in directive to disable transactions; please confirm that your Prisma Migrate version recognizes/* MIGRATION_DISABLE_TRANSACTION */. If it doesn’t, you’ll need to run this migration file manually or via a separate non-transactional execution step (prisma.io).
• Please verify your production Postgres version. If it’s v11 or earlier, disabling the transaction wrapper is mandatory; if it’s v12+, the directive is still harmless and guards against future downgrades or engine quirks.src/lib/errors.ts (2)
265-279: PC report error helpers: consistent and thoroughMessages align with PC-specific flows; approvalTimeNotFound using internalError is appropriate for that code path. No issues spotted.
388-396: Verified developer constraints read wellClear, actionable messages for verification requirements and self-verification prevention. Looks good.
docs/MOBILE_API.md (4)
486-494: New comment actions are well-documentedvoteComment, getUserCommentVotes, and reportComment entries look correct and consistent with existing sections (methods, paths, auth). No issues.
Also applies to: 495-502, 503-511
545-562: PC listings create/update: docs align with new endpointsMethod, path, and auth look correct. Ensure request body schemas are linked in the OpenAPI artifacts (generator should already handle this).
If helpful, I can scan the generated OpenAPI JSON to confirm schema refs.
563-640: Preferences endpoints: coverage looks completeNice addition of granular and bulk operations plus profile retrieval/update. Everything consistently marked as protected with JSON bodies where relevant.
641-648: trust.myInfo: clear, minimal, and consistentEndpoint description matches the trust feature and is correctly protected.
src/app/listings/[id]/components/EditListingModal.tsx (2)
10-10: Good move: centralizing default custom-field initialization.Importing initializeCustomFieldValues reduces duplication and keeps behavior aligned with PC listings.
58-60: Form config consistency with PC modal is a win.Switching to mode: 'onSubmit' and criteriaMode: 'all' keeps UX consistent and avoids noisy validation while typing.
src/app/pc-listings/[id]/components/EditPcListingModal.tsx (2)
14-15: Nice reuse of shared helper.This keeps defaults in sync with console listings and future changes centralized.
69-71: Form config parity looks good.Same rationale as the console modal: less noise during input; all errors on submit; criteriaMode: 'all' gives comprehensive messages.
src/app/listings/components/shared/FormValidationSummary.tsx (1)
20-43: Good coverage of customFieldValues array and root-level errors.Nice handling of array indices and root.message. This should surface most validation failures clearly.
src/app/listings/components/shared/utils/formHelpers.ts (1)
26-40: Helper is concise and handles false/0 defaults correctly.Use of nullish coalescing preserves 0/false defaults. Mapping by definition order ensures alignment with the UI.
src/server/api/routers/listings/core.ts (5)
681-683: Including developerVerifications in featured is fine given take: 3The extra include is small and denormalized UI data helps. No concerns.
503-505: LGTM: first-vote analyticsCounting votes post-insert to detect “first vote” is OK for analytics—even if there’s a race, it only affects event semantics, not state.
901-909: Verification workflow changes look correct
- Includes needed relations (emulator, author) once.
- Properly checks verified-developer status, self-verification, and duplicate verification.
Also applies to: 912-926, 929-931
957-965: Unverify flow is correct and minimalLocates by composite key and deletes by id; returns a simple message. No issues.
885-890: Ignore protectedProcedure vs authorProcedure mismatchThe update mutation in src/server/api/routers/listings/core.ts already enforces ownership (or moderator) at runtime, and all listing authors run with at least Role.USER:
- authorProcedure uses
hasPermission(ctx.session.user.role, Role.USER)(i.e. every signed-in author) before running the mutation- Inside the mutation, there’s an explicit owner check (or moderator override) that returns
canOnlyEditOwn()if you’re neither the author nor a moderatorMoreover, the UI gating is handled by the separate canEdit endpoint (also protectedProcedure), so you won’t show an editable form to someone who can’t save. As a result, there’s no gap between “fetching edit data” and “saving updates.” You can safely disregard the original concern.
Likely an incorrect or invalid review comment.
| - **Description**: Search games | ||
| - **Tags**: games | ||
|
|
There was a problem hiding this comment.
Wrong description for games.getGameById
This should describe fetching a game by ID, not “Search games”.
Apply this diff:
#### 16. **getGameById**
- **Method**: GET
- **Path**: `/games.getGameById`
-- **Description**: Search games
+- **Description**: Get game by ID
- **Tags**: games📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - **Description**: Search games | |
| - **Tags**: games | |
| #### 16. **getGameById** | |
| - **Method**: GET | |
| - **Path**: `/games.getGameById` | |
| - **Description**: Get game by ID | |
| - **Tags**: games |
🧰 Tools
🪛 LanguageTool
[grammar] ~134-~134: There might be a mistake here.
Context: ...ameById` - Description: Search games - Tags: games #### 17. **findSwitchTit...
(QB_NEW_EN)
🤖 Prompt for AI Agents
In docs/MOBILE_API.md around lines 134 to 136 the operation description and tags
for games.getGameById are incorrect — it currently reads "Search games" with
tags "games". Update the description to accurately state that this endpoint
fetches a game by its ID (e.g., "Fetch game by ID" or "Get a game by its ID")
and ensure tags remain appropriate (keep or adjust to "games" if correct).
Replace the existing description line with the corrected text so the doc
reflects the endpoint purpose.
|
Claude finished @Producdevity's task —— View job PR Review Complete ✅I've reviewed this v0.10.0 PR and here's my comprehensive feedback: Todo List:
Overall AssessmentThis PR shows excellent code quality with significant improvements in error handling, validation, and code organization. The changes demonstrate mature software engineering practices. Strengths 🌟1. Error Handling Architecture (
|
…enAPI compatibility
…nd access control
…ization utilities
|
Claude finished @Producdevity's task —— View job PR Review Complete ✅I've reviewed this v0.10.0 PR and here's my comprehensive feedback: Todo List:
Overall AssessmentThis PR shows excellent code quality with significant improvements in error handling, validation, and code organization. The changes demonstrate mature software engineering practices. Strengths 🌟1. Error Handling Architecture (
|
|
Claude finished @Producdevity's task —— View job PR Review Complete ✅I've reviewed this v0.10.0 PR and here's my comprehensive feedback: Todo List:
Overall AssessmentThis PR shows excellent code quality with significant improvements in error handling, validation, and code organization. The changes demonstrate mature software engineering practices. Strengths 🌟1. Error Handling Architecture (
|
|
Claude finished @Producdevity's task —— View job PR Review Complete ✅I've reviewed this v0.10.0 PR and here's my comprehensive feedback: Todo List:
Overall AssessmentThis PR shows excellent code quality with significant improvements in error handling, validation, and code organization. The changes demonstrate mature software engineering practices. Strengths 🌟1. Error Handling Architecture (
|
…ling in admin and user management
…uery in listingVerificationsRouter
|
Claude finished @Producdevity's task —— View job PR Review Complete ✅I've reviewed this v0.10.0 PR and here's my comprehensive feedback: Todo List:
Overall AssessmentThis PR shows excellent code quality with significant improvements in error handling, validation, and code organization. The changes demonstrate mature software engineering practices. Strengths 🌟1. Error Handling Architecture (
|
|
@aicoderabbit full review |
|
Claude finished @Producdevity's task —— View job PR Review Complete ✅I've reviewed this v0.10.0 PR and here's my comprehensive feedback: Todo List:
Overall AssessmentThis PR shows excellent code quality with significant improvements in error handling, validation, and code organization. The changes demonstrate mature software engineering practices. Strengths 🌟1. Error Handling Architecture (
|
|
Claude finished @Producdevity's task —— View job PR Review Complete ✅I've reviewed this v0.10.0 PR and here's my comprehensive feedback: Todo List:
Overall AssessmentThis PR shows excellent code quality with significant improvements in error handling, validation, and code organization. The changes demonstrate mature software engineering practices. Strengths 🌟1. Error Handling Architecture (
|
|
Claude finished @Producdevity's task —— View job I'll review this v0.10.0 PR comprehensively. Let me analyze the changes and provide feedback on code quality, potential bugs, performance, security, and test coverage. Todo List:
Overall AssessmentThis PR shows excellent code quality with significant improvements in error handling, validation, and code organization. The changes demonstrate mature software engineering practices. Strengths 🌟1. Error Handling Architecture (
|
Description
Fixes # (issue)
Type of change
How Has This Been Tested?
Screenshots (if applicable)
Checklist
Notes for reviewers
Summary by CodeRabbit