// This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" previewFeatures = ["driverAdapters"] } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // --- Enums --- enum ParticipantRole { OWNER MANAGER MEMBER MEMBER_NO_FINANCE VIEWER_ONLY } enum JoinRequestStatus { PENDING ACCEPTED REJECTED } enum ExpenseCategory { ACCOMMODATION FOOD TRANSPORT TICKET OTHER } enum LocationStatus { PENDING COMPLETED } enum LocationType { MOVE VISIT REST EAT } enum PrivacyLevel { PUBLIC TOUR_ONLY PRIVATE } // --- Models --- model User { id String @id @default(uuid()) email String? @unique passwordHash String? name String? phone String? address String? avatar String? createdAt DateTime @default(now()) isAdmin Boolean @default(false) isBlocked Boolean @default(false) isAnonymous Boolean @default(false) createdTours Tour[] @relation("TourCreator") tourParticipations TourParticipant[] requestedJoinRequests JoinRequest[] @relation("JoinRequestUser") receivedJoinRequests JoinRequest[] @relation("JoinRequester") uploadedPhotos Photo[] paidExpenses Expense[] @relation("ExpensePaidBy") comments Comment[] sentConnections UserConnection[] @relation("ConnectionRequester") receivedConnections UserConnection[] @relation("ConnectionReceiver") sentMessages DirectMessage[] @relation("MessageSender") receivedMessages DirectMessage[] @relation("MessageReceiver") tourMessages TourMessage[] receivedRatings TourRating[] @relation("RatedUser") sentRatings TourRating[] @relation("RatingUser") tourNotes TourNote[] } model Tour { id String @id @default(uuid()) title String description String? startDate DateTime? endDate DateTime? createdAt DateTime @default(now()) totalCost Decimal @default(0) @db.Decimal(15, 2) adultCount Int @default(1) childCount Int @default(0) childDiscount Int @default(30) tags String[] createdById String creator User @relation("TourCreator", fields: [createdById], references: [id]) participants TourParticipant[] joinRequests JoinRequest[] legs Leg[] photos Photo[] invitations TourInvitation[] tourMessages TourMessage[] ratings TourRating[] share TourShare? notes TourNote[] isDeleted Boolean @default(false) deletedAt DateTime? } model JoinRequest { id String @id @default(uuid()) tourId String userId String requestedById String status JoinRequestStatus @default(PENDING) createdAt DateTime @default(now()) tour Tour @relation(fields: [tourId], references: [id], onDelete: Cascade) user User @relation("JoinRequestUser", fields: [userId], references: [id], onDelete: Cascade) requestedBy User @relation("JoinRequester", fields: [requestedById], references: [id], onDelete: Cascade) @@index([tourId, status]) @@index([userId]) } model TourParticipant { id String @id @default(uuid()) tourId String userId String? role ParticipantRole @default(MEMBER) displayName String? adultCount Int @default(1) childCount Int @default(0) tour Tour @relation(fields: [tourId], references: [id], onDelete: Cascade) user User? @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([tourId, userId]) } model Leg { id String @id @default(uuid()) tourId String sequence Int note String? @db.Text startDate DateTime? endDate DateTime? description String? @db.Text tour Tour @relation(fields: [tourId], references: [id], onDelete: Cascade) locations Location[] expenses Expense[] } model Location { id String @id @default(uuid()) legId String name String address String? latitude Float longitude Float plannedStart DateTime? plannedEnd DateTime? actualStart DateTime? actualEnd DateTime? status LocationStatus @default(PENDING) type LocationType @default(VISIT) leg Leg @relation(fields: [legId], references: [id], onDelete: Cascade) expenses Expense[] photos Photo[] comments Comment[] createdAt DateTime @default(now()) } model Expense { id String @id @default(uuid()) legId String @map("leg_id") locationId String? @map("location_id") category ExpenseCategory amount Decimal @db.Decimal(15, 2) description String? @db.Text note String? @db.Text paidById String? @map("paid_by_id") leg Leg @relation(fields: [legId], references: [id], onDelete: Cascade) location Location? @relation(fields: [locationId], references: [id], onDelete: Cascade) paidBy User? @relation("ExpensePaidBy", fields: [paidById], references: [id], onDelete: SetNull) } model Photo { id String @id @default(uuid()) tourId String? locationId String? uploaderId String imageUrl String? originalUrl String? capturedAt DateTime @default(now()) metadata Json? privacy PrivacyLevel @default(TOUR_ONLY) tour Tour? @relation(fields: [tourId], references: [id], onDelete: SetNull) location Location? @relation(fields: [locationId], references: [id], onDelete: SetNull) uploader User @relation(fields: [uploaderId], references: [id]) comments Comment[] isDeleted Boolean @default(false) deletedAt DateTime? } model Comment { id String @id @default(uuid()) content String createdAt DateTime @default(now()) locationId String? location Location? @relation(fields: [locationId], references: [id], onDelete: Cascade) photoId String? photo Photo? @relation(fields: [photoId], references: [id], onDelete: Cascade) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model TourInvitation { id String @id @default(uuid()) tourId String email String role ParticipantRole @default(MEMBER) token String @unique createdAt DateTime @default(now()) expiredAt DateTime tour Tour @relation(fields: [tourId], references: [id], onDelete: Cascade) @@unique([tourId, email]) } enum ConnectionStatus { PENDING ACCEPTED REJECTED } enum ConnectionType { FRIEND FAMILY } model UserConnection { id String @id @default(uuid()) requesterId String receiverId String status ConnectionStatus @default(PENDING) type ConnectionType @default(FRIEND) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt requester User @relation("ConnectionRequester", fields: [requesterId], references: [id], onDelete: Cascade) receiver User @relation("ConnectionReceiver", fields: [receiverId], references: [id], onDelete: Cascade) @@unique([requesterId, receiverId]) @@index([requesterId]) @@index([receiverId]) } model DirectMessage { id String @id @default(uuid()) senderId String receiverId String content String @db.Text attachmentUrl String? latitude Float? longitude Float? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt sender User @relation("MessageSender", fields: [senderId], references: [id], onDelete: Cascade) receiver User @relation("MessageReceiver", fields: [receiverId], references: [id], onDelete: Cascade) @@index([senderId]) @@index([receiverId]) } model TourMessage { id String @id @default(uuid()) tourId String senderId String content String @db.Text attachmentUrl String? latitude Float? longitude Float? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tour Tour @relation(fields: [tourId], references: [id], onDelete: Cascade) sender User @relation(fields: [senderId], references: [id], onDelete: Cascade) @@index([tourId]) @@index([senderId]) } model WordFilter { id String @id @default(uuid()) word String @unique replacement String createdAt DateTime @default(now()) } model ModerationSetting { id String @id @default(uuid()) blockNsfw Boolean @default(false) blurFaces Boolean @default(false) trashRetentionDays Int @default(30) } model TourRating { id String @id @default(uuid()) tourId String targetUserId String raterUserId String honesty Int @default(5) transparency Int @default(5) enthusiasm Int @default(5) cheerfulness Int @default(5) seriousness Int @default(5) planning Int @default(5) survival Int @default(5) averageScore Float @default(5.0) comment String? @db.Text createdAt DateTime @default(now()) tour Tour @relation(fields: [tourId], references: [id], onDelete: Cascade) targetUser User @relation("RatedUser", fields: [targetUserId], references: [id], onDelete: Cascade) raterUser User @relation("RatingUser", fields: [raterUserId], references: [id], onDelete: Cascade) @@unique([tourId, targetUserId, raterUserId]) } model TourShare { id String @id @default(uuid()) tourId String @unique token String @unique @default(uuid()) isEnabled Boolean @default(true) createdAt DateTime @default(now()) tour Tour @relation(fields: [tourId], references: [id], onDelete: Cascade) } model BusinessReport { id String @id @default(uuid()) type String // e.g. "USER", "RESTAURANT", "HOTEL", "HOMESTAY" name String phone String? email String? address String? latitude Float? longitude Float? reason String @db.Text isBlacklisted Boolean @default(false) createdAt DateTime @default(now()) } model TourNote { id String @id @default(uuid()) tourId String tour Tour @relation(fields: [tourId], references: [id], onDelete: Cascade) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) title String content String @db.Text createdAt DateTime @default(now()) updatedAt DateTime @updatedAt isDeleted Boolean @default(false) deletedAt DateTime? } model RecommendedLocation { id String @id @default(uuid()) type String // e.g. "RESTAURANT", "HOTEL", "HOMESTAY" name String phone String? email String? address String? latitude Float? longitude Float? description String @db.Text stars Int @default(5) isApproved Boolean @default(false) createdAt DateTime @default(now()) }