-
-
Notifications
You must be signed in to change notification settings - Fork 4
(SP: 1) [Cart] adding route for user orders to cart page #337
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
Changes from 17 commits
ec0827e
be244cb
3cadf6d
88f52d3
ea3a437
bfadfe7
9f93a52
1d1852b
2ff41a8
a89fc6a
dd1a02f
155b172
1bc435a
0a7b943
eb42103
cb08926
4e694a4
64b482e
3094c75
d11d3dd
349929c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| import 'server-only'; | ||
|
|
||
| import crypto from 'node:crypto'; | ||
|
|
||
| import { desc, eq, sql } from 'drizzle-orm'; | ||
| import { NextRequest, NextResponse } from 'next/server'; | ||
|
|
||
| import { db } from '@/db'; | ||
| import { orderItems, orders } from '@/db/schema'; | ||
| import { getCurrentUser } from '@/lib/auth'; | ||
| import { logError, logWarn } from '@/lib/logging'; | ||
|
|
||
| export const dynamic = 'force-dynamic'; | ||
|
|
||
| function noStoreJson(body: unknown, init?: { status?: number }) { | ||
| const res = NextResponse.json(body, { status: init?.status ?? 200 }); | ||
| res.headers.set('Cache-Control', 'no-store'); | ||
| return res; | ||
| } | ||
|
|
||
| type PaymentStatus = (typeof orders.$inferSelect)['paymentStatus']; | ||
| type OrderCurrency = (typeof orders.$inferSelect)['currency']; | ||
|
|
||
| function toCount(v: unknown): number { | ||
| let n = 0; | ||
|
|
||
| if (typeof v === 'number') n = v; | ||
| else if (typeof v === 'bigint') n = Number(v); | ||
| else if (typeof v === 'string') n = Number(v); | ||
|
|
||
| if (!Number.isFinite(n)) return 0; | ||
| return Math.max(0, Math.trunc(n)); | ||
| } | ||
|
|
||
| export async function GET(request: NextRequest) { | ||
| const startedAtMs = Date.now(); | ||
| const requestId = | ||
| request.headers.get('x-request-id')?.trim() || crypto.randomUUID(); | ||
|
|
||
| const baseMeta = { | ||
| requestId, | ||
| route: request.nextUrl.pathname, | ||
| method: request.method, | ||
| }; | ||
|
|
||
| try { | ||
| const user = await getCurrentUser(); | ||
| if (!user) { | ||
| logWarn('public_orders_list_unauthorized', { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This route logs Useful? React with 👍 / 👎. |
||
| ...baseMeta, | ||
| code: 'UNAUTHORIZED', | ||
| durationMs: Date.now() - startedAtMs, | ||
| }); | ||
|
|
||
| return noStoreJson( | ||
| { code: 'UNAUTHORIZED', error: 'Authentication required' }, | ||
| { status: 401 } | ||
| ); | ||
| } | ||
|
|
||
| const rows = await db | ||
| .select({ | ||
| id: orders.id, | ||
| totalAmount: orders.totalAmount, | ||
| currency: orders.currency, | ||
| paymentStatus: orders.paymentStatus, | ||
| createdAt: orders.createdAt, | ||
|
|
||
| primaryItemLabel: sql<string | null>` | ||
| ( | ||
| array_agg( | ||
| coalesce( | ||
| nullif(trim(${orderItems.productTitle}), ''), | ||
| nullif(trim(${orderItems.productSlug}), ''), | ||
| nullif(trim(${orderItems.productSku}), '') | ||
| ) | ||
| order by ${orderItems.id} | ||
| ) | ||
| filter ( | ||
| where coalesce( | ||
| nullif(trim(${orderItems.productTitle}), ''), | ||
| nullif(trim(${orderItems.productSlug}), ''), | ||
| nullif(trim(${orderItems.productSku}), '') | ||
| ) is not null | ||
| ) | ||
| )[1] | ||
| `, | ||
| itemCount: sql`count(${orderItems.id})`, | ||
| }) | ||
| .from(orders) | ||
| .leftJoin(orderItems, eq(orderItems.orderId, orders.id)) | ||
| .where(eq(orders.userId, user.id)) | ||
| .groupBy( | ||
| orders.id, | ||
| orders.totalAmount, | ||
| orders.currency, | ||
| orders.paymentStatus, | ||
| orders.createdAt | ||
| ) | ||
| .orderBy(desc(orders.createdAt)) | ||
| .limit(50); | ||
|
coderabbitai[bot] marked this conversation as resolved.
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| const response = rows.map(r => ({ | ||
| id: String(r.id), | ||
| totalAmount: String(r.totalAmount), | ||
| currency: r.currency as OrderCurrency, | ||
| paymentStatus: r.paymentStatus as PaymentStatus, | ||
| createdAt: r.createdAt.toISOString(), | ||
| primaryItemLabel: r.primaryItemLabel ?? null, | ||
| itemCount: toCount(r.itemCount), | ||
| })); | ||
|
|
||
| return noStoreJson({ success: true, orders: response }, { status: 200 }); | ||
| } catch (error) { | ||
| logError('public_orders_list_failed', error, { | ||
| ...baseMeta, | ||
| code: 'PUBLIC_ORDERS_LIST_FAILED', | ||
| durationMs: Date.now() - startedAtMs, | ||
| }); | ||
|
|
||
| return noStoreJson( | ||
| { code: 'INTERNAL_ERROR', error: 'internal_error' }, | ||
| { status: 500 } | ||
| ); | ||
| } | ||
| } | ||
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.
CartPageClientblocks all cart content behind the loader untilhasLoadedOrdersSummarybecomes true, but that flag is only flipped after the/api/shop/ordersrequest settles (including timeout/abort in the effect above). In slow/error cases, this adds up to a 2.5s delay to viewing cart items and starting checkout for a non-essential summary call, which regresses the primary cart flow; render the cart independently and load the orders card asynchronously.Useful? React with 👍 / 👎.