Skip to content
This repository was archived by the owner on May 19, 2026. It is now read-only.

feat: implement API customer get tickets#76

Merged
DungxND merged 5 commits into
mainfrom
feat/24-get-my-tickets
Apr 17, 2026
Merged

feat: implement API customer get tickets#76
DungxND merged 5 commits into
mainfrom
feat/24-get-my-tickets

Conversation

@HungND-flocus
Copy link
Copy Markdown
Contributor

@HungND-flocus HungND-flocus commented Apr 16, 2026

Mô tả

Chức năng Customer có thể xem được vé đã mua với GET /api/me/tickets

Closes #24

Loại thay đổi

  • ✨ Feature mới

Screenshots / Demo

Người dùng chưa mua vé

image

Chưa đăng nhập nhưng vào xem vé

image

Người dùng đã mua vé

image

Checklist

  • Code chạy không lỗi (bun run dev)
  • TypeScript check pass (bun run check)
  • Lint pass (bun run lint)
  • Đã format code (bun run format)
  • Đã test thủ công chức năng
  • Commit message đúng convention (feat:, fix:, chore:,...)

Summary by CodeRabbit

  • New Features

    • Users can retrieve a combined view of their tickets: paid tickets grouped by event with ticket details (seat type/label, show title, start time, payment timestamp) and pending orders listed with expiry and formatted price.
    • General Admission seats hide specific seat labels where applicable.
  • Access

    • Only authenticated customers can view their own ticket data.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2026

📝 Walkthrough

Walkthrough

Adds a new service method getMyOrdersAndTickets(userId) that queries a user's paid tickets and non-expired pending orders with nested relations, transforms them into pending_orders and grouped paid_events, and exposes a new GET API route /api/me/tickets restricted to users with role customer.

Changes

Cohort / File(s) Summary
Service Layer
src/lib/server/services/order.service.ts
Added getMyOrdersAndTickets(userId). New Drizzle query fetching orders where status = 'paid' OR (status = 'pending' AND expiresAt >= now()), eager-loading order_items -> seat -> section -> show -> event. Introduces new TS payload types, mapping logic to produce pending_orders and paid_events, groups paid tickets by event.id, and applies seat_type/seat_label rules (GA → seat_label: null).
API Route
src/routes/api/me/tickets/+server.ts
New GET handler that uses requireAuth(locals), enforces user.role === 'customer' (403 otherwise), calls orderService.getMyOrdersAndTickets(user.id), and returns json({ data: ... }).

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • DungxND

Poem

🐰
I hopped through joins and rows tonight,
Grouped tickets by event beneath the light,
Pending orders wait while paid ones gleam,
GA seats hide numbers — a standing dream,
A little rabbit cheered: "Your tickets look right!" 🎟️

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: implement API customer get tickets' clearly summarizes the main change: adding an API endpoint for customers to retrieve their tickets.
Description check ✅ Passed The PR description follows the template well: includes description of changes, marks the feature type, provides screenshots demonstrating the functionality in three scenarios, and completes all checklist items.
Linked Issues check ✅ Passed The implementation successfully addresses all coding requirements from issue #24: implements getMyOrdersAndTickets service method with proper joins, separates pending/paid orders, handles seat masking for general admission, and adds proper authentication.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #24 requirements: new service method, API route handler, and response structure. No out-of-scope modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/24-get-my-tickets

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9004394 and d9794df.

📒 Files selected for processing (2)
  • src/lib/server/services/order.service.ts
  • src/routes/api/me/tickets/+server.ts

Comment on lines +162 to +187
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,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 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/services

Repository: 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.ts

Repository: 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 2

Repository: 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 3

Repository: 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 -10

Repository: 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 2

Repository: 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 2

Repository: 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 -10

Repository: 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 -300

Repository: 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/null

Repository: 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 -10

Repository: 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 5

Repository: Tixtac-Project/tixtac

Length of output: 605


🏁 Script executed:

# Look at the checkout route
cat src/routes/api/orders/[id]/checkout/+server.ts

Repository: 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/null

Repository: 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 -10

Repository: 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 -100

Repository: 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.ts

Repository: 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 8

Repository: 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/null

Repository: Tixtac-Project/tixtac

Length of output: 49


🏁 Script executed:

# List all routes in the API
find src/routes/api -type f -name '+server.ts' | sort

Repository: 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 5

Repository: 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.ts

Repository: 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 10

Repository: 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 -20

Repository: 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 10

Repository: 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.ts

Repository: 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 ts

Repository: 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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between d9794df and 411aadf.

📒 Files selected for processing (2)
  • src/lib/server/services/order.service.ts
  • src/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

Comment thread src/lib/server/services/order.service.ts Outdated
Comment thread src/lib/server/services/order.service.ts Fixed
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 411aadf and 5b72944.

📒 Files selected for processing (1)
  • src/lib/server/services/order.service.ts

Comment thread src/lib/server/services/order.service.ts Outdated
HungND-flocus and others added 2 commits April 16, 2026 13:25
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/lib/server/services/order.service.ts (2)

235-259: Avoid redefining formatItem inside 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 for getMyOrdersAndTickets.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5b72944 and 3d9d4a3.

📒 Files selected for processing (1)
  • src/lib/server/services/order.service.ts

@DungxND DungxND merged commit 21e98dd into main Apr 17, 2026
2 checks passed
@DungxND DungxND deleted the feat/24-get-my-tickets branch April 17, 2026 12:08
@coderabbitai coderabbitai Bot mentioned this pull request Apr 20, 2026
7 tasks
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BE] API Lấy danh sách Vé và Đơn hàng (My Tickets & Orders)

2 participants