feat: implement API customer get tickets#76
Conversation
📝 WalkthroughWalkthroughAdds a new service method Changes
Sequence DiagramsequenceDiagram
actor Client
participant Handler as API Handler
participant Auth as Auth Service
participant Service as Order Service
participant DB as Database
Client->>Handler: GET /api/me/tickets (with auth)
Handler->>Auth: requireAuth(locals)
Auth-->>Handler: user
Handler->>Handler: check user.role === "customer"
alt not customer
Handler-->>Client: 403 FORBIDDEN
else customer
Handler->>Service: getMyOrdersAndTickets(user.id)
Service->>DB: Query orders WHERE userId=? AND (status='paid' OR (status='pending' AND expiresAt >= now())) ORDER BY createdAt DESC
DB-->>Service: orders with nested relations (order_items → seat → section → show → event)
Service->>Service: map rows → { pending_orders[], paid_events[] } (apply seat_type/seat_label rules)
Service-->>Handler: dashboardData
Handler-->>Client: 200 OK { data: dashboardData }
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
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)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/lib/server/services/order.service.ts`:
- Around line 162-187: The code in the order mapping uses only
firstItemSeat/showInfo/eventInfo (derived from order.items[0]) which silently
assumes all items belong to the same show; modify the mapping in the method that
builds the order DTO (the block using firstItemSeat, showInfo, eventInfo) to
explicitly validate that all order.items have the same show id: iterate
order.items, collect seat.show.id (or null) and if there are multiple distinct
show ids either (a) treat the order-level show/event as null (and optionally log
a warning) or (b) throw/return an error indicating inconsistent shows—pick the
service behavior consistent with your domain; update the returned object to use
the validated single show/event when exactly one show id exists, otherwise
handle as above.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4e8352da-b45e-497d-bb07-4f16a2b6820e
📒 Files selected for processing (2)
src/lib/server/services/order.service.tssrc/routes/api/me/tickets/+server.ts
| const firstItemSeat = order.items[0]?.seat; | ||
| const showInfo = firstItemSeat?.show; | ||
| const eventInfo = showInfo?.event; | ||
|
|
||
| return { | ||
| order_id: order.id, | ||
| total_amount: Number(order.totalAmount).toFixed(2), | ||
| paid_at: order.paidAt ? order.paidAt.toISOString() : null, | ||
|
|
||
| event: eventInfo | ||
| ? { | ||
| id: eventInfo.id, | ||
| title: eventInfo.title, | ||
| venue: eventInfo.venue, | ||
| banner_image_url: eventInfo.bannerImageUrl, | ||
| } | ||
| : null, | ||
|
|
||
| show: showInfo | ||
| ? { | ||
| id: showInfo.id, | ||
| title: showInfo.title, | ||
| show_date: showInfo.showDate, | ||
| start_time: showInfo.startTime, | ||
| } | ||
| : null, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Verify how order_items are created and whether "one show per order" is enforced.
rg -n -C4 --type=ts '\binsert\s*\(\s*orderItems\s*\)|\borderItems\b|\bseatId\b|\borderId\b' src/lib/server/services
# Look for explicit validation/invariant checks around show consistency in order creation flows.
rg -n -C4 --type=ts 'showId|same show|single show|all.*show|seatIds' src/lib/server/servicesRepository: Tixtac-Project/tixtac
Length of output: 31256
🏁 Script executed:
# First, let's see the actual code at lines 162-187 in order.service.ts
sed -n '160,190p' src/lib/server/services/order.service.tsRepository: Tixtac-Project/tixtac
Length of output: 943
🏁 Script executed:
# Now search for where order items are added/created
rg -n 'order.*item|addTo.*order|cart|addItem' src/lib/server/services --type=ts -A 5 -B 2Repository: Tixtac-Project/tixtac
Length of output: 1902
🏁 Script executed:
# Check for any validation that ensures items in an order belong to the same show
rg -n 'show.*valid|same.*show|all.*show|single.*show' src/lib/server/services --type=ts -A 3 -B 3Repository: Tixtac-Project/tixtac
Length of output: 5160
🏁 Script executed:
# Look for the cart service or create order logic
fd -type f -name '*.ts' src/lib/server/services | xargs grep -l 'cart\|createOrder\|addToCart' | head -10Repository: Tixtac-Project/tixtac
Length of output: 235
🏁 Script executed:
# Search for cart-related services or controllers
find src -type f -name '*cart*' -o -name '*order*' | grep -E '\.(ts|js)$'Repository: Tixtac-Project/tixtac
Length of output: 105
🏁 Script executed:
# Search for where items are inserted into orders
rg -n 'orderItems.*insert|insert.*orderItems' src --type ts -A 5 -B 2Repository: Tixtac-Project/tixtac
Length of output: 568
🏁 Script executed:
# Look for cart or order creation routes/endpoints
rg -n 'createOrder|addToCart|addItem' src --type ts -A 10 -B 2Repository: Tixtac-Project/tixtac
Length of output: 47
🏁 Script executed:
# Check database schema to understand relationships
find src -type f -name '*schema*' -o -name '*db*' | grep -E '\.(ts|js)$' | head -10Repository: Tixtac-Project/tixtac
Length of output: 252
🏁 Script executed:
# Check the database schema for order-related tables
cat src/lib/server/db/schema.ts | head -300Repository: Tixtac-Project/tixtac
Length of output: 10470
🏁 Script executed:
# Look for any route handlers for orders or carts
find src/routes -type f -name '*order*' -o -name '*cart*' -o -name '*checkout*' 2>/dev/nullRepository: Tixtac-Project/tixtac
Length of output: 100
🏁 Script executed:
# Search in routes directory for POST/request handlers
find src/routes -type f -name '*.ts' | xargs grep -l 'order\|cart' 2>/dev/null | head -10Repository: Tixtac-Project/tixtac
Length of output: 239
🏁 Script executed:
# Look for where orders are created
rg -n 'orders.*insert|insert.*orders' src --type ts -B 3 -A 5Repository: Tixtac-Project/tixtac
Length of output: 605
🏁 Script executed:
# Look at the checkout route
cat src/routes/api/orders/[id]/checkout/+server.tsRepository: Tixtac-Project/tixtac
Length of output: 1221
🏁 Script executed:
# Search for where orders are created (likely a POST route)
find src/routes -type f -name '*+server.ts' | xargs grep -l 'orders.*insert\|new.*order\|create.*order' 2>/dev/nullRepository: Tixtac-Project/tixtac
Length of output: 49
🏁 Script executed:
# Look for cart-related routes or order creation
find src/routes -type f -name '*+server.ts' | xargs grep -l 'POST\|insert.*order' 2>/dev/null | head -10Repository: Tixtac-Project/tixtac
Length of output: 441
🏁 Script executed:
# Check if there's validation in schemas for order items
rg -n 'order.*schema|cart.*schema|booking.*schema' src/lib/shared/schemas --type ts -A 10 | head -100Repository: Tixtac-Project/tixtac
Length of output: 1067
🏁 Script executed:
# Get the full booking schema to understand order/seat holding
cat src/lib/shared/schemas/booking.schema.tsRepository: Tixtac-Project/tixtac
Length of output: 596
🏁 Script executed:
# Search for where the order is actually created (before checkout)
rg -n 'holdSeats|hold.*seat|reserve.*order' src/lib/server --type ts -B 3 -A 8Repository: Tixtac-Project/tixtac
Length of output: 47
🏁 Script executed:
# Find the seat holding/reservation route
find src/routes -type f -name '*+server.ts' | xargs grep -l 'hold\|reserve' 2>/dev/nullRepository: Tixtac-Project/tixtac
Length of output: 49
🏁 Script executed:
# List all routes in the API
find src/routes/api -type f -name '+server.ts' | sortRepository: Tixtac-Project/tixtac
Length of output: 800
🏁 Script executed:
# Search more broadly for order creation logic
rg -n 'holdSeatsSchema|create.*order|new.*order' src/lib/server --type ts -B 2 -A 5Repository: Tixtac-Project/tixtac
Length of output: 692
🏁 Script executed:
# Look for any validation that checks seat showId
rg -n 'showId|show.*id' src/lib/server/services --type ts -A 2 -B 2 | grep -A 5 -B 5 'seat\|order'Repository: Tixtac-Project/tixtac
Length of output: 11187
🏁 Script executed:
# Search for the route that holds/locks seats and creates an order
find src/routes -type f -name '+server.ts' -exec grep -l 'lockSeats\|holdSeats\|cart' {} \;Repository: Tixtac-Project/tixtac
Length of output: 47
🏁 Script executed:
# Look in the seats route for the hold/lock logic
cat src/routes/api/events/[id]/shows/[showId]/seats/+server.tsRepository: Tixtac-Project/tixtac
Length of output: 825
🏁 Script executed:
# Search for any service that handles seat locking/holding
rg -n 'lock.*seat|hold.*seat' src/lib/server/services --type ts -B 3 -A 10Repository: Tixtac-Project/tixtac
Length of output: 47
🏁 Script executed:
# Search all service files for order/seat operations
ls -la src/lib/server/services/Repository: Tixtac-Project/tixtac
Length of output: 514
🏁 Script executed:
# Look for any service that creates orders or manages cart
rg -n 'export.*order|export.*seat' src/lib/server/services --type ts | head -20Repository: Tixtac-Project/tixtac
Length of output: 210
🏁 Script executed:
# Search for where orders table is inserted (create order)
rg -n '\.insert\(orders\)' src/lib/server --type ts -B 5 -A 10Repository: Tixtac-Project/tixtac
Length of output: 1038
🏁 Script executed:
# Look for all POST routes that might create orders
find src/routes -type f -name '+server.ts' -exec grep -l 'POST' {} \;Repository: Tixtac-Project/tixtac
Length of output: 441
🏁 Script executed:
# Read the complete order.service.ts to see all exported methods
cat src/lib/server/services/order.service.tsRepository: Tixtac-Project/tixtac
Length of output: 5862
🏁 Script executed:
# Search for any POST routes that might accept seat_ids and create an order
find src/routes -type f -name '+server.ts' -exec grep -H 'seat.*id\|order' {} \;Repository: Tixtac-Project/tixtac
Length of output: 778
🏁 Script executed:
# Check if there's a check constraint or trigger ensuring same show per order
cat src/lib/server/db/schema.ts | grep -A 30 "orderItems\|orders"Repository: Tixtac-Project/tixtac
Length of output: 3764
🏁 Script executed:
# Search for any validation logic that might check if all seats in an order belong to same show
rg -n 'same.*show|all.*seat.*show|order.*item.*show' src/lib/server --type tsRepository: Tixtac-Project/tixtac
Length of output: 171
Validate the single-show assumption at order level.
Lines 162–164 derive event/show from only the first item. The database schema has no constraint enforcing that all seats in an order belong to the same show, so if an order ever contains seats from different shows, the order-level metadata becomes incorrect.
Either add a database constraint to enforce this invariant, validate it at item creation time, or document the assumption clearly.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/server/services/order.service.ts` around lines 162 - 187, The code in
the order mapping uses only firstItemSeat/showInfo/eventInfo (derived from
order.items[0]) which silently assumes all items belong to the same show; modify
the mapping in the method that builds the order DTO (the block using
firstItemSeat, showInfo, eventInfo) to explicitly validate that all order.items
have the same show id: iterate order.items, collect seat.show.id (or null) and
if there are multiple distinct show ids either (a) treat the order-level
show/event as null (and optionally log a warning) or (b) throw/return an error
indicating inconsistent shows—pick the service behavior consistent with your
domain; update the returned object to use the validated single show/event when
exactly one show id exists, otherwise handle as above.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/lib/server/services/order.service.ts`:
- Around line 301-323: The reduction into a plain object (paidEventsMap) uses
numeric keys so Object.values(paidEventsMap) will return entries ordered by
event_id rather than the original insertion order from paidTicketsFlatList;
change the accumulator to a Map<number, PaidEventEntry> (or build a
preserved-order array) inside the reduce that uses paidTicketsFlatList, update
the logic that checks/creates entries (referencing paidTicketsFlatList,
paidEventsMap, PaidEventEntry), and return Array.from(paidEventsMap.values())
for paid_events so insertion order (recency) is preserved while keeping
pending_orders unchanged.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: a8b7f359-503c-4f3f-a1f4-0a8b6d5645a1
📒 Files selected for processing (2)
src/lib/server/services/order.service.tssrc/routes/api/me/tickets/+server.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/routes/api/me/tickets/+server.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/lib/server/services/order.service.ts`:
- Line 4: The pending-orders boundary is inconsistent: replace the gte usage
with a strict gt so pending orders expiring exactly at now are treated as
expired like checkout() does; update the import list to include gt (remove gte)
from 'drizzle-orm' and change the condition in getMyOrdersAndTickets() from
gte(orders.expiresAt, now) to gt(orders.expiresAt, now) so the query and
checkout() share the same exclusive expiry semantics.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: ac76ee35-64a5-43c5-a019-748f4349273f
📒 Files selected for processing (1)
src/lib/server/services/order.service.ts
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/lib/server/services/order.service.ts (2)
235-259: Avoid redefiningformatIteminside each order iteration.Moving this formatter outside the loop reduces per-iteration allocations and improves readability.
Suggested refactor
- for (const order of userOrders) { - const formatItem = (item: OrderItemWithRelations): FormattedItem => { + const formatItem = (item: OrderItemWithRelations): FormattedItem => { const seat = item.seat; const section = seat.section; const isGeneral = section.type === 'general'; @@ return { event: seat.show.event, show: seat.show, item_id: item.id, price: Number(item.priceSnapshot).toFixed(2), ticket_code: item.ticketCode, @@ seat_label: seatLabel, }; - }; + }; + + for (const order of userOrders) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/server/services/order.service.ts` around lines 235 - 259, The formatItem function is being redefined inside the for (const order of userOrders) loop; extract it as a standalone helper (e.g., const formatItem = (item: OrderItemWithRelations): FormattedItem => { ... }) declared once outside the loop so it isn't reallocated per iteration, ensuring it only references the item and related properties (seat, section, show) and does not close over order; update the loop to call this shared formatItem for each OrderItemWithRelations, keeping the same output shape (event, show, item_id, price, ticket_code, section_name, seat_type, seat_label).
205-230: Add a pagination cap forgetMyOrdersAndTickets.This query currently loads all matching orders with deep relations in one shot. For heavy users, this can become slow and produce very large response bodies.
Suggested patch (service-side safety cap)
async getMyOrdersAndTickets(userId: number) { + const HISTORY_PAGE_SIZE = 100; /** * 1. Query: Lấy tất cả đơn hàng (đã thanh toán hoặc đang chờ thanh toán mà chưa hết hạn). * Query trực tiếp điều kiện thời gian, thay vì lấy hết rồi mới lọc. */ const now = new Date(); const userOrders = await db.query.orders.findMany({ @@ orderBy: [desc(orders.createdAt)], + limit: HISTORY_PAGE_SIZE, with: {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/server/services/order.service.ts` around lines 205 - 230, The query in getMyOrdersAndTickets that calls db.query.orders.findMany (assigning to userOrders) loads all matching orders and deep relations, so add a server-side pagination cap: introduce a MAX_ORDERS constant (e.g. 100) and accept/derive a requested limit/offset, then pass a bounded limit (e.g. take: Math.min(requestedLimit || MAX_ORDERS, MAX_ORDERS)) and optional skip/offset into db.query.orders.findMany instead of unbounded fetch; update the call site in getMyOrdersAndTickets to enforce this cap and return pagination metadata so clients know results are limited.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/lib/server/services/order.service.ts`:
- Around line 235-259: The formatItem function is being redefined inside the for
(const order of userOrders) loop; extract it as a standalone helper (e.g., const
formatItem = (item: OrderItemWithRelations): FormattedItem => { ... }) declared
once outside the loop so it isn't reallocated per iteration, ensuring it only
references the item and related properties (seat, section, show) and does not
close over order; update the loop to call this shared formatItem for each
OrderItemWithRelations, keeping the same output shape (event, show, item_id,
price, ticket_code, section_name, seat_type, seat_label).
- Around line 205-230: The query in getMyOrdersAndTickets that calls
db.query.orders.findMany (assigning to userOrders) loads all matching orders and
deep relations, so add a server-side pagination cap: introduce a MAX_ORDERS
constant (e.g. 100) and accept/derive a requested limit/offset, then pass a
bounded limit (e.g. take: Math.min(requestedLimit || MAX_ORDERS, MAX_ORDERS))
and optional skip/offset into db.query.orders.findMany instead of unbounded
fetch; update the call site in getMyOrdersAndTickets to enforce this cap and
return pagination metadata so clients know results are limited.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f58a308f-bd35-4f1a-8e04-1adc4b7a6413
📒 Files selected for processing (1)
src/lib/server/services/order.service.ts
Mô tả
Chức năng Customer có thể xem được vé đã mua với
GET /api/me/ticketsCloses #24
Loại thay đổi
Screenshots / Demo
Người dùng chưa mua vé
Chưa đăng nhập nhưng vào xem vé
Người dùng đã mua vé
Checklist
bun run dev)bun run check)bun run lint)bun run format)feat:,fix:,chore:,...)Summary by CodeRabbit
New Features
Access