Initial marketplace implementation
Full-stack marketplace for buying/selling second-hand items. React 19 + TypeScript + Tailwind CSS v4 frontend with 17 screens, Express + Prisma + Socket.io backend, Stripe payments, JWT auth. Deployed at https://marketplace.173.212.212.157.sslip.io/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
262
server/prisma/schema.prisma
Normal file
262
server/prisma/schema.prisma
Normal file
@@ -0,0 +1,262 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
enum Category {
|
||||
ELECTRONICS
|
||||
FURNITURE
|
||||
CLOTHING
|
||||
HOME_GARDEN
|
||||
SPORTS
|
||||
BOOKS
|
||||
GAMES
|
||||
VEHICLES
|
||||
OTHER
|
||||
}
|
||||
|
||||
enum ListingCondition {
|
||||
NEW
|
||||
LIKE_NEW
|
||||
GENTLY_USED
|
||||
USED
|
||||
FAIR
|
||||
}
|
||||
|
||||
enum ListingStatus {
|
||||
DRAFT
|
||||
ACTIVE
|
||||
SOLD
|
||||
DELETED
|
||||
}
|
||||
|
||||
enum OfferStatus {
|
||||
PENDING
|
||||
ACCEPTED
|
||||
DECLINED
|
||||
COUNTERED
|
||||
}
|
||||
|
||||
enum NotificationType {
|
||||
NEW_OFFER
|
||||
OFFER_ACCEPTED
|
||||
OFFER_DECLINED
|
||||
ITEM_SOLD
|
||||
NEW_MESSAGE
|
||||
ITEM_FAVORITED
|
||||
}
|
||||
|
||||
enum PaymentStatus {
|
||||
PENDING
|
||||
COMPLETED
|
||||
FAILED
|
||||
REFUNDED
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
email String @unique
|
||||
passwordHash String
|
||||
fullName String
|
||||
nickname String?
|
||||
avatar String?
|
||||
phone String?
|
||||
location String?
|
||||
bio String?
|
||||
rating Float @default(0)
|
||||
ratingCount Int @default(0)
|
||||
showEmail Boolean @default(false)
|
||||
showPhone Boolean @default(true)
|
||||
showLocation Boolean @default(true)
|
||||
showOnline Boolean @default(true)
|
||||
showRating Boolean @default(true)
|
||||
twoFactorEnabled Boolean @default(false)
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
sessions Session[]
|
||||
listings Listing[]
|
||||
images ListingImage[]
|
||||
sentOffers Offer[] @relation("BuyerOffers")
|
||||
receivedOffers Offer[] @relation("SellerOffers")
|
||||
conversations1 Conversation[] @relation("User1Conversations")
|
||||
conversations2 Conversation[] @relation("User2Conversations")
|
||||
messages Message[]
|
||||
favorites Favorite[]
|
||||
notifications Notification[]
|
||||
payments Payment[]
|
||||
blockedUsers BlockedUser[] @relation("Blocker")
|
||||
blockedBy BlockedUser[] @relation("Blocked")
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
refreshToken String @unique
|
||||
userAgent String?
|
||||
ipAddress String?
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
model Listing {
|
||||
id String @id @default(cuid())
|
||||
title String
|
||||
description String
|
||||
price Float
|
||||
obo Boolean @default(false)
|
||||
category Category
|
||||
condition ListingCondition
|
||||
status ListingStatus @default(DRAFT)
|
||||
location String
|
||||
viewCount Int @default(0)
|
||||
sellerId String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
seller User @relation(fields: [sellerId], references: [id], onDelete: Cascade)
|
||||
images ListingImage[]
|
||||
offers Offer[]
|
||||
conversations Conversation[]
|
||||
favorites Favorite[]
|
||||
payments Payment[]
|
||||
|
||||
@@index([sellerId])
|
||||
@@index([category])
|
||||
@@index([status])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
model ListingImage {
|
||||
id String @id @default(cuid())
|
||||
url String
|
||||
order Int @default(0)
|
||||
listingId String
|
||||
uploadedBy String
|
||||
|
||||
listing Listing @relation(fields: [listingId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [uploadedBy], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([listingId])
|
||||
}
|
||||
|
||||
model Offer {
|
||||
id String @id @default(cuid())
|
||||
amount Float
|
||||
message String?
|
||||
status OfferStatus @default(PENDING)
|
||||
counterAmount Float?
|
||||
buyerId String
|
||||
sellerId String
|
||||
listingId String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
buyer User @relation("BuyerOffers", fields: [buyerId], references: [id], onDelete: Cascade)
|
||||
seller User @relation("SellerOffers", fields: [sellerId], references: [id], onDelete: Cascade)
|
||||
listing Listing @relation(fields: [listingId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([buyerId])
|
||||
@@index([sellerId])
|
||||
@@index([listingId])
|
||||
}
|
||||
|
||||
model Conversation {
|
||||
id String @id @default(cuid())
|
||||
user1Id String
|
||||
user2Id String
|
||||
listingId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
user1 User @relation("User1Conversations", fields: [user1Id], references: [id], onDelete: Cascade)
|
||||
user2 User @relation("User2Conversations", fields: [user2Id], references: [id], onDelete: Cascade)
|
||||
listing Listing? @relation(fields: [listingId], references: [id], onDelete: SetNull)
|
||||
messages Message[]
|
||||
|
||||
@@unique([user1Id, user2Id, listingId])
|
||||
@@index([user1Id])
|
||||
@@index([user2Id])
|
||||
}
|
||||
|
||||
model Message {
|
||||
id String @id @default(cuid())
|
||||
content String
|
||||
senderId String
|
||||
conversationId String
|
||||
isRead Boolean @default(false)
|
||||
offerAmount Float?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
sender User @relation(fields: [senderId], references: [id], onDelete: Cascade)
|
||||
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([conversationId])
|
||||
@@index([senderId])
|
||||
}
|
||||
|
||||
model Favorite {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
listingId String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
listing Listing @relation(fields: [listingId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([userId, listingId])
|
||||
}
|
||||
|
||||
model Notification {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
type NotificationType
|
||||
title String
|
||||
body String
|
||||
data Json?
|
||||
isRead Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
model Payment {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
listingId String
|
||||
stripePaymentId String? @unique
|
||||
amount Float
|
||||
status PaymentStatus @default(PENDING)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
listing Listing @relation(fields: [listingId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId])
|
||||
@@index([listingId])
|
||||
}
|
||||
|
||||
model BlockedUser {
|
||||
id String @id @default(cuid())
|
||||
blockerId String
|
||||
blockedId String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
blocker User @relation("Blocker", fields: [blockerId], references: [id], onDelete: Cascade)
|
||||
blocked User @relation("Blocked", fields: [blockedId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([blockerId, blockedId])
|
||||
}
|
||||
Reference in New Issue
Block a user