Domain: Event
Scenario: Event Lifecycle (Create → Update → Sync → Complete)
1. Business Goal
Event is the core entity for ticket-based commerce (flash sales, live shows, parties). When a curator creates an Event, the system automatically creates:
- A Product (ticket type) linked to the Event
- A Post (social commerce content) for promoters to resell
- Guest management (invitee list with check-in/verification)
2. Trigger & Entry
| API |
Method |
Description |
Request Body |
/product-event/v2 |
POST |
Create event (V2 - auto-creates post) |
{ title, startDate, venue, location, poster, tickets[], lineups[] } |
/product-event/:id |
PUT |
Update event |
Event update DTO |
/product-event/:id/details |
GET |
Get event details |
- |
/product-event/:id/related-posts |
GET |
Get related posts |
- |
/posts/curator/from-event |
POST |
Create post from event |
CreatePostRequest |
/posts/curator/toggle-sync |
POST |
Toggle event sync |
{ postId, syncEnabled } |
3. Data Flow (Sequence)
sequenceDiagram
participant Curator as Curator (User)
participant API as EventController
participant Service as ProductEventV2Service
participant DB as PostgreSQL
participant Queue as BullMQ
participant Post as PostModule
participant Shopify as Shopify (optional)
Curator->>API: POST /product-event/v2
API->>Service: createEvent()
Note over Service: 1. Validate input
Service->>DB: BEGIN TRANSACTION
Note over Service: 2. Create Event record
Service->>DB: INSERT Event
DB-->>Service: event.id
Note over Service: 3. Create EventMedia records
Service->>DB: INSERT EventMedia[]
Note over Service: 4. Create EventLineup records
Service->>DB: INSERT EventLineup[]
Note over Service: 5. Create Product (ticket) linked to Event
Service->>DB: INSERT Product (eventId=event.id)
Note over Service: 6. Create ProductVariant for each ticket
Service->>DB: INSERT ProductVariant[]
Service->>DB: COMMIT
Note over Service: 7. Create auto-post (if enabled)
Service->>Post: createPostFromEvent()
Post->>DB: INSERT Post (createFromEventId, postType=EVENT)
Note over Service: 8. Trigger downstream sync
Service->>Queue: event.created (productId, eventId)
Queue->>Shopify: Sync product to Shopify (optional)
Service-->>Curator: { event, post, product }
4. DB Operations (Chronological)
| Step |
Table |
Action |
PK/FK Touched |
Notes |
| 1 |
Event |
INSERT |
id (PK), curatorId (FK) |
status = UPCOMING |
| 2 |
EventMedia |
INSERT |
id (PK), eventId (FK) |
poster images |
| 3 |
EventLineup |
INSERT |
id (PK), eventId (FK) |
performers/speakers |
| 4 |
Product |
INSERT |
id (PK), eventId (FK), merchantId (FK) |
listingType = TICKET |
| 5 |
ProductVariant |
INSERT |
id (PK), productId (FK) |
ticket types (GA, VIP, etc.) |
| 6 |
Post |
INSERT |
id (PK), creatorId (FK), createFromEventId (FK) |
postType = EVENT |
| 7 |
PostMedia |
INSERT |
id (PK), postId (FK) |
copy from EventMedia |
5. Event/Queue Messages
| Message |
Producer → Consumer |
Payload Snippet |
event.created |
EventModule → ProductQueue |
{ eventId, productId, curatorId } |
event.updated |
EventModule → PostSyncQueue |
{ oldEvent, newEvent, syncCategories } |
event.status.changed |
EventModule → ProductQueue |
{ eventId, oldStatus, newStatus } |
event.completed |
CronJob → EventModule |
{ eventIds[] } |
6. Event → Post Sync Logic
When syncEnabled = true on a Post, Event changes propagate:
Event.title → Post.headline
Event.title → Post.title
Event.venue + date → Post.subTitle
Event.poster → Post.media, Post.coverImages
type SyncCategory = 'metadata' | 'variants';
Trigger: Event update → ProductEventPublisher → POST_SYNC queue → EventSyncTrigger
7. File Map (Top 5 Must-Read)
| File |
Purpose |
src/product-event/product-event.controller.ts |
HTTP endpoints |
src/product-event-v2/product-event-v2.service.ts |
V2 create logic (auto-post) |
src/product-event-core/product-event.entity.ts |
Domain entity |
src/posts/curator/posts-curator.service.ts |
Post creation from event |
src/post-sync/event-sync-trigger.ts |
Sync orchestration |
8. DB Schema Snippet (Prisma)
model Event {
id String @id @default(uuid()) @db.Uuid
curatorId String @map("curator_id") @db.Uuid
status EventStatus @default(UPCOMING)
title String
startDate DateTime @map("start_date") @db.Timestamptz()
endDate DateTime? @map("end_date") @db.Timestamptz()
venue String
location String
poster Json? // [ProductCoverImage]
allowAutoComplete Boolean @default(true)
messageInfo Json @default("{\"smsMaxLimit\": 3}")
isAddressRevealEnabled Boolean @default(false)
addressRevealConfig Json?
isTaxEnabled Boolean @default(false)
taxConfig Json?
deletedAt DateTime? @map("deleted_at")
// Relations
product Product[]
lineups EventLineup[]
medias EventMedia[]
messages EventMessage[]
guests EventGuestToEvent[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
}
model EventLineup {
id String @id @db.Uuid
eventId String @map("event_id") @db.Uuid
title String
introduction String?
poster Json?
order Int @default(0)
isHeadliner Boolean @default(false)
Event Event @relation(fields: [eventId], references: [id])
@@index([eventId])
}
model EventMedia {
id String @id @db.Uuid
eventId String @map("event_id") @db.Uuid
src String
position Int
mediaType MediaType @default(IMAGE)
Event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
@@index([eventId])
}
enum EventStatus {
UPCOMING
ON_HOLD
CANCELED
COMPLETED
}
9. Event Status Flow
stateDiagram-v2
[*] --> UPCOMING: Create event
UPCOMING --> ON_HOLD: Manual pause
ON_HOLD --> UPCOMING: Resume
UPCOMING --> COMPLETED: endDate passed + autoComplete
UPCOMING --> CANCELED: Manual cancel
COMPLETED --> [*]
CANCELED --> [*]
10. Guest Management (KAT-8848)
| API |
Method |
Description |
/product-event/guests |
POST |
Create guest |
/product-event/guests/groups |
POST |
Create guest group |
/product-event/:eventId/guests |
POST |
Add guests to event |
/product-event/guest-verification/check-in |
POST |
Guest check-in |
model EventGuest {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
firstName String @map("first_name")
lastName String @map("last_name")
fullName String @map("full_name")
email String?
phoneNumber String? @map("phone_number")
groups EventGuestToGroup[]
events EventGuestToEvent[]
deletedAt DateTime? @map("deleted_at")
}
model EventGuestToEvent {
id String @id @default(uuid()) @db.Uuid
eventId String @map("event_id") @db.Uuid
guestId String @map("guest_id") @db.Uuid
status EventGuestStatus @default(INVITED)
checkedInAt DateTime? @map("checked_in_at")
checkedInBy String? @map("checked_in_by") @db.Uuid
}