-
Notifications
You must be signed in to change notification settings - Fork 0
Fix lint errors, API response format, optimize database queries, and create comprehensive audit summary #129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix lint errors, API response format, optimize database queries, and create comprehensive audit summary #129
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…, fix Date.now() impure function error Co-authored-by: rezwana-karim <126201034+rezwana-karim@users.noreply.github.com>
Co-authored-by: rezwana-karim <126201034+rezwana-karim@users.noreply.github.com>
Co-authored-by: rezwana-karim <126201034+rezwana-karim@users.noreply.github.com>
Co-authored-by: rezwana-karim <126201034+rezwana-karim@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 78 out of 93 changed files in this pull request and generated 7 comments.
| export interface RouteContext<T extends Record<string, string> = Record<string, string>> { | ||
| params: Promise<T>; | ||
| } |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The RouteContext interface is well-designed for Next.js 16+ dynamic routes. Consider adding a JSDoc comment explaining that this type is specifically for App Router dynamic route segments where params are now async. This would help other developers understand why params is a Promise.
| const cache = new Map<string, CacheEntry<unknown>>(); | ||
| const inFlightRequests = new Map<string, InFlightRequest>(); | ||
| const subscribers = new Set<() => void>(); |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The module-level cache implementation is excellent for request deduplication. However, consider documenting the memory implications and potential cache size limits. For production applications with many unique API calls, this cache could grow unbounded. Consider adding a maximum cache size or implementing an LRU eviction policy.
| async jwt({ token, user, trigger }) { | ||
| // Initial sign-in: Enrich token with user context | ||
| if (user) { | ||
| token.sub = user.id; | ||
| token.isSuperAdmin = (user as { isSuperAdmin?: boolean }).isSuperAdmin || false; | ||
| } |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The optimization to move database queries from the session callback to the jwt callback is excellent. This reduces database queries from every session access to only during JWT refresh. However, ensure that the JWT secret rotation strategy is documented, as cached permissions in tokens won't reflect role changes until the next JWT refresh.
src/components/orders-table.tsx
Outdated
| const intervalId = setInterval(() => { | ||
| checkForUpdatesRef.current(); // Use ref to prevent interval restart | ||
| }, 30000); // 30 seconds | ||
| }, 60000); // 60 seconds |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Increasing the polling interval from 30s to 60s is a good optimization to reduce database load. However, this could impact the user experience for time-sensitive order updates. Consider implementing a WebSocket or Server-Sent Events solution for real-time updates instead of polling, or make the polling interval configurable based on user preferences.
| const [ | ||
| currentRevenueResult, | ||
| currentOrdersCount, | ||
| currentCustomersCount, | ||
| productsCount, | ||
| previousRevenueResult, | ||
| previousOrdersCount, | ||
| previousCustomersCount, | ||
| ] = await prisma.$transaction([ | ||
| // Current period revenue | ||
| prisma.order.aggregate({ | ||
| where: { | ||
| storeId, | ||
| createdAt: { gte: from, lte: to }, | ||
| status: { in: [OrderStatus.PAID, OrderStatus.PROCESSING, OrderStatus.SHIPPED, OrderStatus.DELIVERED] }, | ||
| deletedAt: null, | ||
| }, | ||
| _sum: { totalAmount: true }, | ||
| }), | ||
| // Current period orders | ||
| prisma.order.count({ | ||
| where: { | ||
| storeId, | ||
| createdAt: { gte: from, lte: to }, | ||
| deletedAt: null, | ||
| }, | ||
| }), | ||
| // Current period new customers | ||
| prisma.customer.count({ | ||
| where: { | ||
| storeId, | ||
| createdAt: { gte: from, lte: to }, | ||
| deletedAt: null, | ||
| }, | ||
| }), | ||
| // Total active products (not period-specific) | ||
| prisma.product.count({ | ||
| where: { | ||
| storeId, | ||
| deletedAt: null, | ||
| status: 'ACTIVE', | ||
| }, | ||
| }), | ||
| // Previous period revenue | ||
| prisma.order.aggregate({ | ||
| where: { | ||
| storeId, | ||
| createdAt: { gte: previousFrom, lte: previousTo }, | ||
| status: { in: [OrderStatus.PAID, OrderStatus.PROCESSING, OrderStatus.SHIPPED, OrderStatus.DELIVERED] }, | ||
| deletedAt: null, | ||
| }, | ||
| _sum: { totalAmount: true }, | ||
| }), | ||
| // Previous period orders | ||
| prisma.order.count({ | ||
| where: { | ||
| storeId, | ||
| createdAt: { gte: previousFrom, lte: previousTo }, | ||
| deletedAt: null, | ||
| }, | ||
| }), | ||
| // Previous period new customers | ||
| prisma.customer.count({ | ||
| where: { | ||
| storeId, | ||
| createdAt: { gte: previousFrom, lte: previousTo }, | ||
| deletedAt: null, | ||
| }, | ||
| }), | ||
| ]); | ||
|
|
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The $transaction optimization is excellent, reducing 7 database queries to 1 round-trip. However, consider that if any single query in the transaction fails, all queries are rolled back. Ensure that the calling code has appropriate error handling for this scenario.
| const [ | |
| currentRevenueResult, | |
| currentOrdersCount, | |
| currentCustomersCount, | |
| productsCount, | |
| previousRevenueResult, | |
| previousOrdersCount, | |
| previousCustomersCount, | |
| ] = await prisma.$transaction([ | |
| // Current period revenue | |
| prisma.order.aggregate({ | |
| where: { | |
| storeId, | |
| createdAt: { gte: from, lte: to }, | |
| status: { in: [OrderStatus.PAID, OrderStatus.PROCESSING, OrderStatus.SHIPPED, OrderStatus.DELIVERED] }, | |
| deletedAt: null, | |
| }, | |
| _sum: { totalAmount: true }, | |
| }), | |
| // Current period orders | |
| prisma.order.count({ | |
| where: { | |
| storeId, | |
| createdAt: { gte: from, lte: to }, | |
| deletedAt: null, | |
| }, | |
| }), | |
| // Current period new customers | |
| prisma.customer.count({ | |
| where: { | |
| storeId, | |
| createdAt: { gte: from, lte: to }, | |
| deletedAt: null, | |
| }, | |
| }), | |
| // Total active products (not period-specific) | |
| prisma.product.count({ | |
| where: { | |
| storeId, | |
| deletedAt: null, | |
| status: 'ACTIVE', | |
| }, | |
| }), | |
| // Previous period revenue | |
| prisma.order.aggregate({ | |
| where: { | |
| storeId, | |
| createdAt: { gte: previousFrom, lte: previousTo }, | |
| status: { in: [OrderStatus.PAID, OrderStatus.PROCESSING, OrderStatus.SHIPPED, OrderStatus.DELIVERED] }, | |
| deletedAt: null, | |
| }, | |
| _sum: { totalAmount: true }, | |
| }), | |
| // Previous period orders | |
| prisma.order.count({ | |
| where: { | |
| storeId, | |
| createdAt: { gte: previousFrom, lte: previousTo }, | |
| deletedAt: null, | |
| }, | |
| }), | |
| // Previous period new customers | |
| prisma.customer.count({ | |
| where: { | |
| storeId, | |
| createdAt: { gte: previousFrom, lte: previousTo }, | |
| deletedAt: null, | |
| }, | |
| }), | |
| ]); | |
| let currentRevenueResult; | |
| let currentOrdersCount; | |
| let currentCustomersCount; | |
| let productsCount; | |
| let previousRevenueResult; | |
| let previousOrdersCount; | |
| let previousCustomersCount; | |
| try { | |
| [ | |
| currentRevenueResult, | |
| currentOrdersCount, | |
| currentCustomersCount, | |
| productsCount, | |
| previousRevenueResult, | |
| previousOrdersCount, | |
| previousCustomersCount, | |
| ] = await prisma.$transaction([ | |
| // Current period revenue | |
| prisma.order.aggregate({ | |
| where: { | |
| storeId, | |
| createdAt: { gte: from, lte: to }, | |
| status: { in: [OrderStatus.PAID, OrderStatus.PROCESSING, OrderStatus.SHIPPED, OrderStatus.DELIVERED] }, | |
| deletedAt: null, | |
| }, | |
| _sum: { totalAmount: true }, | |
| }), | |
| // Current period orders | |
| prisma.order.count({ | |
| where: { | |
| storeId, | |
| createdAt: { gte: from, lte: to }, | |
| deletedAt: null, | |
| }, | |
| }), | |
| // Current period new customers | |
| prisma.customer.count({ | |
| where: { | |
| storeId, | |
| createdAt: { gte: from, lte: to }, | |
| deletedAt: null, | |
| }, | |
| }), | |
| // Total active products (not period-specific) | |
| prisma.product.count({ | |
| where: { | |
| storeId, | |
| deletedAt: null, | |
| status: 'ACTIVE', | |
| }, | |
| }), | |
| // Previous period revenue | |
| prisma.order.aggregate({ | |
| where: { | |
| storeId, | |
| createdAt: { gte: previousFrom, lte: previousTo }, | |
| status: { in: [OrderStatus.PAID, OrderStatus.PROCESSING, OrderStatus.SHIPPED, OrderStatus.DELIVERED] }, | |
| deletedAt: null, | |
| }, | |
| _sum: { totalAmount: true }, | |
| }), | |
| // Previous period orders | |
| prisma.order.count({ | |
| where: { | |
| storeId, | |
| createdAt: { gte: previousFrom, lte: previousTo }, | |
| deletedAt: null, | |
| }, | |
| }), | |
| // Previous period new customers | |
| prisma.customer.count({ | |
| where: { | |
| storeId, | |
| createdAt: { gte: previousFrom, lte: previousTo }, | |
| deletedAt: null, | |
| }, | |
| }), | |
| ]); | |
| } catch (error) { | |
| // Provide contextual information for transaction failures | |
| // while allowing upstream handlers to decide how to respond. | |
| console.error('Failed to fetch dashboard stats via Prisma transaction', { | |
| storeId, | |
| from, | |
| to, | |
| error, | |
| }); | |
| throw error; | |
| } |
| /** | ||
| * @vitest-environment jsdom | ||
| * | ||
| * Component Tests for Card | ||
| * | ||
| * Tests the Card component and its sub-components. | ||
| */ |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test suite is comprehensive for the Card component. However, according to the repository instructions, there is no test suite infrastructure and tests should not be added unless explicitly requested. These tests should not have been added as part of this PR.
| export function invalidateQueries(pattern?: string | RegExp) { | ||
| if (!pattern) { | ||
| cache.clear(); |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cache invalidation logic handles clearing all caches, exact matches, and pattern matches. However, the string matching logic at line 406 uses startsWith for prefix matching which could lead to unintended cache invalidations. For example, invalidating /api/stores would also invalidate /api/stores-backup if that route existed. Consider using more precise URL matching or documenting this behavior clearly.
|
@copilot apply changes based on the comments in this thread implement all the suggestions and improvements based on the comments also research online for best practices for all the improvements and make sure to start with reviewing the codebase across api, ui and database schema and use all the latest documentations and guides of Next.js 16 and Shadcn UI for all the improvements |
…, better URL matching Co-authored-by: syed-reza98 <71028588+syed-reza98@users.noreply.github.com>
…x, and code review fixes Co-authored-by: syed-reza98 <71028588+syed-reza98@users.noreply.github.com>
… fallback Co-authored-by: syed-reza98 <71028588+syed-reza98@users.noreply.github.com>
Implemented all the code review suggestions with real features (not just documentation): Commit dde7279 - Server-Sent Events for real-time order updates:
Commit 8341df4 - LRU cache and improved error handling:
All features validated with browser automation on real data (36 orders in Demo Store). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 75 out of 95 changed files in this pull request and generated 3 comments.
|
|
||
| const getDaysUntilRenewal = (endDate: string) => { | ||
| const days = Math.ceil((new Date(endDate).getTime() - Date.now()) / (1000 * 60 * 60 * 24)); | ||
| const days = Math.ceil((new Date(endDate).getTime() - currentTimestamp) / (1000 * 60 * 60 * 24)); |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable currentTimestamp is captured once when the component mounts but never updates. If the component stays mounted for extended periods (e.g., hours or days), the "days until renewal" calculation will become increasingly inaccurate because it's comparing against a stale timestamp. Consider using Date.now() directly in the calculation, or update currentTimestamp periodically if you need to reduce re-renders.
src/hooks/useApiQuery.ts
Outdated
| // Run cleanup every 60 seconds (client-side only) | ||
| if (typeof window !== 'undefined') { | ||
| cleanupIntervalId = setInterval(cleanupExpiredEntries, 60 * 1000); |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cleanup interval runs every 60 seconds regardless of cache activity. In low-traffic scenarios, this creates unnecessary background work. Consider implementing an on-demand cleanup strategy that runs during cache access operations, or increase the interval for production environments to reduce CPU overhead.
| // Run cleanup every 60 seconds (client-side only) | |
| if (typeof window !== 'undefined') { | |
| cleanupIntervalId = setInterval(cleanupExpiredEntries, 60 * 1000); | |
| // Use a more conservative cleanup interval in production to reduce CPU overhead. | |
| // Keep the original 60s interval in non-production for faster feedback during development. | |
| const CLEANUP_INTERVAL_MS = | |
| typeof process !== 'undefined' && process.env && process.env.NODE_ENV === 'production' | |
| ? 5 * 60 * 1000 // 5 minutes in production | |
| : 60 * 1000; // 60 seconds in development/test | |
| // Run cleanup on a fixed interval (client-side only) | |
| if (typeof window !== 'undefined') { | |
| cleanupIntervalId = setInterval(cleanupExpiredEntries, CLEANUP_INTERVAL_MS); |
| const dbUser = await prisma.user.findUnique({ | ||
| where: { id: userId }, | ||
| include: { | ||
| select: { |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The user context fetch in the JWT callback includes nested relations (memberships.organization.store, storeStaff) which could be expensive for users with many memberships. Consider adding a limit to the memberships query or implementing pagination if users can belong to many organizations.
|
@copilot apply changes based on the comments in this thread |
…cache cleanup, and configurable query limits Co-authored-by: rafiqul4 <124497017+rafiqul4@users.noreply.github.com>
Applied all code review suggestions in commit d173a38:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is being reviewed by Cursor Bugbot
Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
| # For development (SQLite): | ||
| # DATABASE_URL="file:./dev.db" | ||
| DATABASE_URL="postgres://62f4097df5e872956ef3438a631f543fae4d5d42215bd0826950ab47ae13d1d8:sk_C9LGde4N8GzIwZvatfrYp@db.prisma.io:5432/postgres?sslmode=require" | ||
| DATABASE_URL="postgres://00a2b80f79491981d1bb3b2e9f16ff38e4f8ec8176d81850c1a0fc6b8d07aedb:sk_SAURAAr96utLcyihkDPJ7@db.prisma.io:5432/postgres?sslmode=require" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Real database credentials committed to env example file
The DATABASE_URL in .env.example contains what appears to be a real Prisma Data Proxy credential (sk_SAURAAr96utLcyihkDPJ7). The sk_ prefix is Prisma's standard format for secret keys. This file is tracked by git and visible to anyone with repository access. The .env.example file should contain placeholder values like postgres://user:password@localhost:5432/dbname rather than actual credentials. Anyone with access to this repository can now use these credentials to access the database.
21cfbd6
into
copilot/identify-improve-slow-code
Code Review Suggestions Applied
Addressed all code review comments from PR review #3607877648:
1. Fixed stale timestamp in subscriptions-list.tsx (Comment #2643055426)
Problem:
currentTimestampwas captured once when component mounts but never updated, causing inaccurate "days until renewal" calculations for long-running sessions.Solution: Added periodic timestamp updates via
useEffect:2. Environment-based cleanup interval in useApiQuery.ts (Comment #2643055463)
Problem: Cleanup interval running every 60 seconds creates unnecessary background work in low-traffic scenarios.
Solution: Implemented environment-based interval:
processcheck is needed (browser environment)3. Configurable query limits in auth.ts (Comment #2643055502)
Problem: User context fetch includes expensive nested relations (memberships, storeStaff) that could be slow for users with many memberships.
Solution:
MAX_JWT_MEMBERSHIP_FETCHconstant (default: 10)MAX_JWT_MEMBERSHIP_FETCHenvironment variableorderBy: { createdAt: 'desc' }to get most recent records firsttake: MAX_JWT_MEMBERSHIP_FETCHlimit to both queriesValidation
Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.
Note
Docs/Planning
COMPREHENSIVE_ENHANCEMENT_PLAN.md,QUICK_REFERENCE.md,audit-summary.md,docs/COMPREHENSIVE_PR_ANALYSIS_DEC_2025.md).github/prompts/plan-storefrontThemeCustomization.prompt.md)Testing
e2e/*.spec.ts,e2e/fixtures.ts)Build/Config
next.config.ts(experimental) alongside React Compiler.env.examplewith Postgres URL and email placeholders; adds VSCodefiles.autoSavelint-errors.jsonsnapshotWritten by Cursor Bugbot for commit d173a38. This will update automatically on new commits. Configure here.