Files
marketplace/server/prisma/seed.ts
delta-lynx-89e8 68beca8f30 Update seed with real rental image paths
Replace placeholder image paths with actual downloaded images
for all 6 rental categories (15 images total).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 15:39:50 -08:00

1119 lines
44 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { PrismaClient } from '@prisma/client';
import bcryptjs from 'bcryptjs';
const { hashSync } = bcryptjs;
const prisma = new PrismaClient();
async function main() {
console.log('Seeding database...');
const passwordHash = hashSync('password123', 10);
// ── Clear existing data (reverse dependency order) ──────────────────
await prisma.$transaction([
prisma.promotedRental.deleteMany(),
prisma.rentalFavorite.deleteMany(),
prisma.rentalReview.deleteMany(),
prisma.payout.deleteMany(),
prisma.bookingPayment.deleteMany(),
prisma.booking.deleteMany(),
prisma.availabilityBlock.deleteMany(),
prisma.rentalImage.deleteMany(),
prisma.rentalListing.deleteMany(),
prisma.moderationLog.deleteMany(),
prisma.promotedListing.deleteMany(),
prisma.subscription.deleteMany(),
prisma.report.deleteMany(),
prisma.platformConfig.deleteMany(),
prisma.message.deleteMany(),
prisma.conversation.deleteMany(),
prisma.notification.deleteMany(),
prisma.payment.deleteMany(),
prisma.favorite.deleteMany(),
prisma.offer.deleteMany(),
prisma.listingImage.deleteMany(),
prisma.listing.deleteMany(),
prisma.blockedUser.deleteMany(),
prisma.session.deleteMany(),
prisma.user.deleteMany(),
]);
console.log('Cleared existing data.');
// ── Users ───────────────────────────────────────────────────────────
const users = await prisma.$transaction([
prisma.user.create({
data: {
id: 'user-alice',
email: 'alice.chen@example.com',
passwordHash,
fullName: 'Alice Chen',
nickname: 'alice',
avatar: '/uploads/avatars/alice.jpg',
phone: '(415) 555-0101',
location: 'San Francisco, CA',
bio: 'Tech enthusiast and avid reader. Always looking for great deals on gadgets and books.',
rating: 4.8,
ratingCount: 24,
showEmail: false,
showPhone: true,
showLocation: true,
showOnline: true,
showRating: true,
isActive: true,
marketingEmail: true,
},
}),
prisma.user.create({
data: {
id: 'user-bob',
email: 'bob.martinez@example.com',
passwordHash,
fullName: 'Bob Martinez',
nickname: 'bobby_m',
avatar: '/uploads/avatars/bob.jpg',
phone: '(213) 555-0202',
location: 'Los Angeles, CA',
bio: 'Sports memorabilia collector and weekend warrior. Selling gear I no longer use.',
rating: 4.5,
ratingCount: 18,
showEmail: true,
showPhone: true,
showLocation: true,
showOnline: true,
showRating: true,
isActive: true,
},
}),
prisma.user.create({
data: {
id: 'user-carol',
email: 'carol.nguyen@example.com',
passwordHash,
fullName: 'Carol Nguyen',
nickname: 'carol_n',
avatar: '/uploads/avatars/carol.jpg',
phone: '(503) 555-0303',
location: 'Portland, OR',
bio: 'Interior designer with an eye for vintage furniture. Downsizing my collection.',
rating: 4.9,
ratingCount: 32,
showEmail: false,
showPhone: false,
showLocation: true,
showOnline: false,
showRating: true,
isActive: true,
twoFactorEnabled: true,
},
}),
prisma.user.create({
data: {
id: 'user-david',
email: 'david.kim@example.com',
passwordHash,
fullName: 'David Kim',
nickname: 'dave_k',
avatar: '/uploads/avatars/david.jpg',
phone: '(312) 555-0404',
location: 'Chicago, IL',
bio: 'Gamer and music lover. Clearing out my shelves to make room for new hobbies.',
rating: 4.2,
ratingCount: 11,
showEmail: false,
showPhone: true,
showLocation: true,
showOnline: true,
showRating: true,
isActive: true,
},
}),
prisma.user.create({
data: {
id: 'user-eva',
email: 'eva.johnson@example.com',
passwordHash,
fullName: 'Eva Johnson',
nickname: 'eva_j',
avatar: '/uploads/avatars/eva.jpg',
phone: '(206) 555-0505',
location: 'Seattle, WA',
bio: 'Outdoor enthusiast and plant mom. Selling items that need a new home.',
rating: 4.7,
ratingCount: 15,
showEmail: true,
showPhone: true,
showLocation: true,
showOnline: true,
showRating: true,
isActive: true,
notifFavorites: false,
},
}),
]);
console.log(`Created ${users.length} users.`);
// ── Listings ────────────────────────────────────────────────────────
const listingsData = [
// ACTIVE listings (10)
{
id: 'listing-01',
title: 'MacBook Pro 14" M3 Pro — 18GB RAM',
description: 'Barely used MacBook Pro 14-inch with M3 Pro chip, 18GB unified memory, 512GB SSD. Includes original charger and box. AppleCare+ until March 2027. No scratches or dents, always used with a case.',
price: 1650.00,
obo: true,
category: 'ELECTRONICS' as const,
condition: 'LIKE_NEW' as const,
status: 'ACTIVE' as const,
location: 'San Francisco, CA',
viewCount: 142,
sellerId: 'user-alice',
},
{
id: 'listing-02',
title: 'Mid-Century Modern Walnut Coffee Table',
description: 'Beautiful solid walnut coffee table with tapered legs. Dimensions: 48"L x 24"W x 16"H. Minor surface wear consistent with age. A real statement piece for any living room.',
price: 320.00,
obo: true,
category: 'FURNITURE' as const,
condition: 'GENTLY_USED' as const,
status: 'ACTIVE' as const,
location: 'Portland, OR',
viewCount: 87,
sellerId: 'user-carol',
},
{
id: 'listing-03',
title: 'Patagonia Better Sweater Jacket — Men\'s Large',
description: 'Classic Patagonia fleece jacket in "New Navy" colorway, size Large. Worn a handful of times, no pilling or stains. Great for layering or casual wear.',
price: 75.00,
obo: false,
category: 'CLOTHING' as const,
condition: 'LIKE_NEW' as const,
status: 'ACTIVE' as const,
location: 'Seattle, WA',
viewCount: 63,
sellerId: 'user-eva',
},
{
id: 'listing-04',
title: 'Weber Spirit II E-310 Gas Grill',
description: 'Three-burner propane grill with side tables. Used for two summers. GS4 grilling system, porcelain-enameled cast-iron grates. Includes cover and propane tank. Pickup only.',
price: 280.00,
obo: true,
category: 'HOME_GARDEN' as const,
condition: 'USED' as const,
status: 'ACTIVE' as const,
location: 'Chicago, IL',
viewCount: 54,
sellerId: 'user-david',
},
{
id: 'listing-05',
title: 'Trek Marlin 7 Mountain Bike — Size M',
description: '2024 Trek Marlin 7 in Matte Nautical Navy. Size Medium (fits 5\'5"5\'9"). Shimano Deore 1x10 drivetrain, RockShox Judy fork. About 300 miles on it. Ready to ride.',
price: 620.00,
obo: true,
category: 'SPORTS' as const,
condition: 'GENTLY_USED' as const,
status: 'ACTIVE' as const,
location: 'Portland, OR',
viewCount: 109,
sellerId: 'user-carol',
},
{
id: 'listing-06',
title: 'Complete Dune Series (6 Books, Hardcover)',
description: 'The original six Dune novels by Frank Herbert in hardcover. Ace/Putnam editions with dust jackets. All in very good condition — no writing, highlighting, or torn pages.',
price: 95.00,
obo: false,
category: 'BOOKS' as const,
condition: 'GENTLY_USED' as const,
status: 'ACTIVE' as const,
location: 'San Francisco, CA',
viewCount: 76,
sellerId: 'user-alice',
},
{
id: 'listing-07',
title: 'PlayStation 5 Disc Edition + 4 Games',
description: 'PS5 Disc Edition console with two DualSense controllers, charging dock, and four games: Spider-Man 2, FF7 Rebirth, Elden Ring, and Baldur\'s Gate 3. Everything works perfectly.',
price: 420.00,
obo: true,
category: 'GAMES' as const,
condition: 'GENTLY_USED' as const,
status: 'ACTIVE' as const,
location: 'Chicago, IL',
viewCount: 203,
sellerId: 'user-david',
},
{
id: 'listing-08',
title: 'Sony WH-1000XM5 Wireless Headphones',
description: 'Industry-leading noise cancelling headphones in black. Includes carrying case, USB-C cable, and audio cable. Battery still holds 30+ hours. Ear pads in excellent shape.',
price: 199.00,
obo: false,
category: 'ELECTRONICS' as const,
condition: 'LIKE_NEW' as const,
status: 'ACTIVE' as const,
location: 'Los Angeles, CA',
viewCount: 91,
sellerId: 'user-bob',
},
{
id: 'listing-09',
title: 'Dyson V12 Detect Slim Cordless Vacuum',
description: 'Powerful cordless stick vacuum with laser dust detection. Comes with all original attachments and wall-mount dock. About 1 year old, works flawlessly.',
price: 340.00,
obo: true,
category: 'HOME_GARDEN' as const,
condition: 'GENTLY_USED' as const,
status: 'ACTIVE' as const,
location: 'Seattle, WA',
viewCount: 68,
sellerId: 'user-eva',
},
{
id: 'listing-10',
title: 'Vintage Schwinn Varsity Road Bike',
description: '1975 Schwinn Varsity 10-speed in original Kool Lemon color. All-original components, rides well. Some cosmetic patina adds character. A classic commuter or collector piece.',
price: 250.00,
obo: true,
category: 'VEHICLES' as const,
condition: 'FAIR' as const,
status: 'ACTIVE' as const,
location: 'Los Angeles, CA',
viewCount: 45,
sellerId: 'user-bob',
},
// SOLD listings (3)
{
id: 'listing-11',
title: 'Nintendo Switch OLED + Pro Controller',
description: 'White Nintendo Switch OLED model with Pro Controller, dock, and carrying case. Screen is pristine. Includes 3 physical game cartridges.',
price: 300.00,
obo: false,
category: 'GAMES' as const,
condition: 'GENTLY_USED' as const,
status: 'SOLD' as const,
location: 'San Francisco, CA',
viewCount: 178,
sellerId: 'user-alice',
},
{
id: 'listing-12',
title: 'Herman Miller Aeron Chair — Size B',
description: 'Fully loaded Aeron chair in graphite. Size B (medium). PostureFit SL, adjustable arms, tilt limiter. Purchased from authorized dealer in 2022. 12-year warranty still active.',
price: 750.00,
obo: true,
category: 'FURNITURE' as const,
condition: 'LIKE_NEW' as const,
status: 'SOLD' as const,
location: 'Portland, OR',
viewCount: 256,
sellerId: 'user-carol',
},
{
id: 'listing-13',
title: 'Canon EOS R6 Mark II Body Only',
description: 'Professional mirrorless camera body. 24.2MP full-frame sensor, 4K 60fps video, IBIS. Shutter count under 5,000. Includes battery, charger, and strap. No box.',
price: 1800.00,
obo: true,
category: 'ELECTRONICS' as const,
condition: 'LIKE_NEW' as const,
status: 'SOLD' as const,
location: 'Chicago, IL',
viewCount: 312,
sellerId: 'user-david',
},
// DRAFT listings (2)
{
id: 'listing-14',
title: 'Garmin Fenix 7X Solar Smartwatch',
description: 'Multi-sport GPS watch with solar charging. Titanium bezel, sapphire lens. Includes QuickFit bands. Still drafting — need to take photos.',
price: 450.00,
obo: true,
category: 'ELECTRONICS' as const,
condition: 'GENTLY_USED' as const,
status: 'DRAFT' as const,
location: 'Seattle, WA',
viewCount: 0,
sellerId: 'user-eva',
},
{
id: 'listing-15',
title: 'Restoration Hardware Cloud Sofa — 2 Seat',
description: 'RH Cloud sofa in white perennials fabric. Extremely comfortable. Moving and cannot bring it. Will update with measurements and photos shortly.',
price: 2200.00,
obo: true,
category: 'FURNITURE' as const,
condition: 'USED' as const,
status: 'DRAFT' as const,
location: 'Los Angeles, CA',
viewCount: 0,
sellerId: 'user-bob',
},
];
const listings = await prisma.$transaction(
listingsData.map((l) => prisma.listing.create({ data: l }))
);
console.log(`Created ${listings.length} listings.`);
// ── Listing Images ──────────────────────────────────────────────────
const imageRecords = [
{ listingId: 'listing-01', uploadedBy: 'user-alice', url: '/uploads/placeholder-1.jpg', order: 0 },
{ listingId: 'listing-01', uploadedBy: 'user-alice', url: '/uploads/placeholder-2.jpg', order: 1 },
{ listingId: 'listing-02', uploadedBy: 'user-carol', url: '/uploads/placeholder-3.jpg', order: 0 },
{ listingId: 'listing-02', uploadedBy: 'user-carol', url: '/uploads/placeholder-4.jpg', order: 1 },
{ listingId: 'listing-03', uploadedBy: 'user-eva', url: '/uploads/placeholder-5.jpg', order: 0 },
{ listingId: 'listing-04', uploadedBy: 'user-david', url: '/uploads/placeholder-6.jpg', order: 0 },
{ listingId: 'listing-04', uploadedBy: 'user-david', url: '/uploads/placeholder-7.jpg', order: 1 },
{ listingId: 'listing-05', uploadedBy: 'user-carol', url: '/uploads/placeholder-8.jpg', order: 0 },
{ listingId: 'listing-05', uploadedBy: 'user-carol', url: '/uploads/placeholder-9.jpg', order: 1 },
{ listingId: 'listing-06', uploadedBy: 'user-alice', url: '/uploads/placeholder-10.jpg', order: 0 },
{ listingId: 'listing-07', uploadedBy: 'user-david', url: '/uploads/placeholder-11.jpg', order: 0 },
{ listingId: 'listing-07', uploadedBy: 'user-david', url: '/uploads/placeholder-12.jpg', order: 1 },
{ listingId: 'listing-08', uploadedBy: 'user-bob', url: '/uploads/placeholder-13.jpg', order: 0 },
{ listingId: 'listing-09', uploadedBy: 'user-eva', url: '/uploads/placeholder-14.jpg', order: 0 },
{ listingId: 'listing-10', uploadedBy: 'user-bob', url: '/uploads/placeholder-15.jpg', order: 0 },
{ listingId: 'listing-10', uploadedBy: 'user-bob', url: '/uploads/placeholder-16.jpg', order: 1 },
{ listingId: 'listing-11', uploadedBy: 'user-alice', url: '/uploads/placeholder-17.jpg', order: 0 },
{ listingId: 'listing-12', uploadedBy: 'user-carol', url: '/uploads/placeholder-18.jpg', order: 0 },
{ listingId: 'listing-12', uploadedBy: 'user-carol', url: '/uploads/placeholder-19.jpg', order: 1 },
{ listingId: 'listing-13', uploadedBy: 'user-david', url: '/uploads/placeholder-20.jpg', order: 0 },
{ listingId: 'listing-13', uploadedBy: 'user-david', url: '/uploads/placeholder-21.jpg', order: 1 },
];
const images = await prisma.$transaction(
imageRecords.map((img) => prisma.listingImage.create({ data: img }))
);
console.log(`Created ${images.length} listing images.`);
// ── Offers ──────────────────────────────────────────────────────────
const now = new Date();
const inOneWeek = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
const daysAgo = (d: number) => new Date(now.getTime() - d * 24 * 60 * 60 * 1000);
const offersData = [
// ACCEPTED offers on SOLD listings
{
id: 'offer-01',
amount: 275.00,
message: 'Would you take $275? I can pick it up today.',
status: 'ACCEPTED' as const,
buyerId: 'user-bob',
sellerId: 'user-alice',
listingId: 'listing-11',
expiresAt: daysAgo(-2),
createdAt: daysAgo(10),
},
{
id: 'offer-02',
amount: 680.00,
message: 'Beautiful chair! I have been looking for one of these. Would $680 work?',
status: 'ACCEPTED' as const,
counterAmount: 720.00,
buyerId: 'user-eva',
sellerId: 'user-carol',
listingId: 'listing-12',
expiresAt: daysAgo(-1),
createdAt: daysAgo(14),
},
{
id: 'offer-03',
amount: 1700.00,
message: 'Interested in the R6 II. Offering $1700 — can meet anywhere in Chicago.',
status: 'ACCEPTED' as const,
buyerId: 'user-alice',
sellerId: 'user-david',
listingId: 'listing-13',
expiresAt: daysAgo(-3),
createdAt: daysAgo(7),
},
// PENDING offers
{
id: 'offer-04',
amount: 1500.00,
message: 'Is there any flexibility on price? I see similar configs going for around $1500.',
status: 'PENDING' as const,
buyerId: 'user-david',
sellerId: 'user-alice',
listingId: 'listing-01',
expiresAt: inOneWeek,
createdAt: daysAgo(1),
},
{
id: 'offer-05',
amount: 380.00,
message: 'Great deal on the PS5 bundle. Would you accept $380?',
status: 'PENDING' as const,
buyerId: 'user-carol',
sellerId: 'user-david',
listingId: 'listing-07',
expiresAt: inOneWeek,
createdAt: daysAgo(2),
},
{
id: 'offer-06',
amount: 550.00,
message: 'Love this bike! Would you consider $550?',
status: 'PENDING' as const,
buyerId: 'user-alice',
sellerId: 'user-carol',
listingId: 'listing-05',
expiresAt: inOneWeek,
createdAt: daysAgo(1),
},
// DECLINED offers
{
id: 'offer-07',
amount: 200.00,
message: 'I can offer $200 for the coffee table.',
status: 'DECLINED' as const,
buyerId: 'user-david',
sellerId: 'user-carol',
listingId: 'listing-02',
expiresAt: daysAgo(1),
createdAt: daysAgo(5),
},
{
id: 'offer-08',
amount: 150.00,
message: 'Budget is tight but really want those headphones. $150?',
status: 'DECLINED' as const,
buyerId: 'user-eva',
sellerId: 'user-bob',
listingId: 'listing-08',
expiresAt: daysAgo(2),
createdAt: daysAgo(8),
},
// COUNTERED offers
{
id: 'offer-09',
amount: 250.00,
message: 'Interested in the grill. How about $250?',
status: 'COUNTERED' as const,
counterAmount: 270.00,
buyerId: 'user-alice',
sellerId: 'user-david',
listingId: 'listing-04',
expiresAt: inOneWeek,
createdAt: daysAgo(3),
},
{
id: 'offer-10',
amount: 290.00,
message: 'Would $290 work for the vacuum? I am in the Ballard area and can pick up.',
status: 'COUNTERED' as const,
counterAmount: 310.00,
buyerId: 'user-bob',
sellerId: 'user-eva',
listingId: 'listing-09',
expiresAt: inOneWeek,
createdAt: daysAgo(2),
},
];
const offers = await prisma.$transaction(
offersData.map((o) => prisma.offer.create({ data: o }))
);
console.log(`Created ${offers.length} offers.`);
// ── Conversations & Messages ────────────────────────────────────────
const conv1 = await prisma.conversation.create({
data: {
id: 'conv-01',
user1Id: 'user-bob',
user2Id: 'user-alice',
listingId: 'listing-11',
},
});
const conv2 = await prisma.conversation.create({
data: {
id: 'conv-02',
user1Id: 'user-eva',
user2Id: 'user-carol',
listingId: 'listing-12',
},
});
const conv3 = await prisma.conversation.create({
data: {
id: 'conv-03',
user1Id: 'user-david',
user2Id: 'user-alice',
listingId: 'listing-01',
},
});
const conv4 = await prisma.conversation.create({
data: {
id: 'conv-04',
user1Id: 'user-alice',
user2Id: 'user-david',
listingId: 'listing-04',
},
});
console.log('Created 4 conversations.');
const messagesData = [
// Conv 1: Bob buying Switch from Alice (completed sale)
{ content: 'Hi! Is the Switch still available?', senderId: 'user-bob', conversationId: 'conv-01', isRead: true, createdAt: daysAgo(11) },
{ content: 'Yes it is! Everything works great.', senderId: 'user-alice', conversationId: 'conv-01', isRead: true, createdAt: daysAgo(11) },
{ content: 'Awesome. Would you take $275 for it?', senderId: 'user-bob', conversationId: 'conv-01', isRead: true, offerAmount: 275.00, createdAt: daysAgo(10) },
{ content: 'That works for me. When can you pick up?', senderId: 'user-alice', conversationId: 'conv-01', isRead: true, createdAt: daysAgo(10) },
{ content: 'How about Saturday around noon? I can come to the Mission district.', senderId: 'user-bob', conversationId: 'conv-01', isRead: true, createdAt: daysAgo(9) },
{ content: 'Perfect. I will DM you the exact address on Saturday morning.', senderId: 'user-alice', conversationId: 'conv-01', isRead: true, createdAt: daysAgo(9) },
{ content: 'Picked it up — everything looks great. Thanks Alice!', senderId: 'user-bob', conversationId: 'conv-01', isRead: true, createdAt: daysAgo(7) },
// Conv 2: Eva buying Aeron from Carol (completed sale)
{ content: 'Hi Carol, love the Aeron listing! Is the warranty transferable?', senderId: 'user-eva', conversationId: 'conv-02', isRead: true, createdAt: daysAgo(15) },
{ content: 'Hi Eva! Yes, Herman Miller warranties follow the chair, not the owner.', senderId: 'user-carol', conversationId: 'conv-02', isRead: true, createdAt: daysAgo(15) },
{ content: 'Great to know. Would $680 be fair? I see refurbs going for about that.', senderId: 'user-eva', conversationId: 'conv-02', isRead: true, offerAmount: 680.00, createdAt: daysAgo(14) },
{ content: 'This is from an authorized dealer though. How about we meet at $720?', senderId: 'user-carol', conversationId: 'conv-02', isRead: true, offerAmount: 720.00, createdAt: daysAgo(14) },
{ content: '$720 is fair. Deal! Can you ship to Seattle or is it pickup only?', senderId: 'user-eva', conversationId: 'conv-02', isRead: true, createdAt: daysAgo(13) },
{ content: 'I can ship via UPS freight. I will factor that into the price — still $720 total.', senderId: 'user-carol', conversationId: 'conv-02', isRead: true, createdAt: daysAgo(13) },
{ content: 'That is incredibly generous. Sending payment now.', senderId: 'user-eva', conversationId: 'conv-02', isRead: true, createdAt: daysAgo(12) },
{ content: 'Payment received! Shipping Monday. Will send tracking.', senderId: 'user-carol', conversationId: 'conv-02', isRead: true, createdAt: daysAgo(12) },
// Conv 3: David interested in MacBook from Alice (ongoing)
{ content: 'Hey, quick question about the MacBook — what is the battery cycle count?', senderId: 'user-david', conversationId: 'conv-03', isRead: true, createdAt: daysAgo(3) },
{ content: 'Let me check... 47 cycles. Basically brand new battery.', senderId: 'user-alice', conversationId: 'conv-03', isRead: true, createdAt: daysAgo(3) },
{ content: 'Nice. Any issues with the keyboard or trackpad?', senderId: 'user-david', conversationId: 'conv-03', isRead: true, createdAt: daysAgo(2) },
{ content: 'Nope, everything is perfect. I mostly used it with an external display and keyboard.', senderId: 'user-alice', conversationId: 'conv-03', isRead: true, createdAt: daysAgo(2) },
{ content: 'I submitted an offer for $1500 — let me know what you think.', senderId: 'user-david', conversationId: 'conv-03', isRead: true, offerAmount: 1500.00, createdAt: daysAgo(1) },
{ content: 'I will take a look. That is a bit lower than I was hoping for but let me think about it.', senderId: 'user-alice', conversationId: 'conv-03', isRead: false, createdAt: daysAgo(0) },
// Conv 4: Alice interested in grill from David (ongoing)
{ content: 'Hi David! Does the grill come with the cover shown in the photo?', senderId: 'user-alice', conversationId: 'conv-04', isRead: true, createdAt: daysAgo(4) },
{ content: 'Yes, the cover and propane tank are both included.', senderId: 'user-david', conversationId: 'conv-04', isRead: true, createdAt: daysAgo(4) },
{ content: 'I put in an offer for $250. Let me know if that works.', senderId: 'user-alice', conversationId: 'conv-04', isRead: true, offerAmount: 250.00, createdAt: daysAgo(3) },
{ content: 'Appreciate the offer! Could you do $270? I paid $500 new last year.', senderId: 'user-david', conversationId: 'conv-04', isRead: true, offerAmount: 270.00, createdAt: daysAgo(3) },
{ content: 'Let me think on it and get back to you.', senderId: 'user-alice', conversationId: 'conv-04', isRead: false, createdAt: daysAgo(2) },
];
const messages = await prisma.$transaction(
messagesData.map((m) => prisma.message.create({ data: m }))
);
console.log(`Created ${messages.length} messages.`);
// ── Favorites ───────────────────────────────────────────────────────
const favoritesData = [
{ userId: 'user-bob', listingId: 'listing-01' },
{ userId: 'user-bob', listingId: 'listing-06' },
{ userId: 'user-carol', listingId: 'listing-07' },
{ userId: 'user-carol', listingId: 'listing-08' },
{ userId: 'user-david', listingId: 'listing-02' },
{ userId: 'user-david', listingId: 'listing-05' },
{ userId: 'user-eva', listingId: 'listing-01' },
{ userId: 'user-eva', listingId: 'listing-07' },
{ userId: 'user-alice', listingId: 'listing-04' },
{ userId: 'user-alice', listingId: 'listing-05' },
{ userId: 'user-alice', listingId: 'listing-08' },
];
const favorites = await prisma.$transaction(
favoritesData.map((f) => prisma.favorite.create({ data: f }))
);
console.log(`Created ${favorites.length} favorites.`);
// ── Notifications ───────────────────────────────────────────────────
const notificationsData = [
// NEW_OFFER
{
userId: 'user-alice',
type: 'NEW_OFFER' as const,
title: 'New Offer Received',
body: 'David Kim offered $1,500 for your MacBook Pro 14" M3 Pro.',
data: { offerId: 'offer-04', listingId: 'listing-01' },
isRead: false,
createdAt: daysAgo(1),
},
{
userId: 'user-david',
type: 'NEW_OFFER' as const,
title: 'New Offer Received',
body: 'Carol Nguyen offered $380 for your PlayStation 5 Disc Edition.',
data: { offerId: 'offer-05', listingId: 'listing-07' },
isRead: true,
createdAt: daysAgo(2),
},
// OFFER_ACCEPTED
{
userId: 'user-bob',
type: 'OFFER_ACCEPTED' as const,
title: 'Offer Accepted!',
body: 'Alice Chen accepted your offer of $275 for the Nintendo Switch OLED.',
data: { offerId: 'offer-01', listingId: 'listing-11' },
isRead: true,
createdAt: daysAgo(10),
},
{
userId: 'user-alice',
type: 'OFFER_ACCEPTED' as const,
title: 'Offer Accepted!',
body: 'David Kim accepted your offer of $1,700 for the Canon EOS R6 Mark II.',
data: { offerId: 'offer-03', listingId: 'listing-13' },
isRead: true,
createdAt: daysAgo(7),
},
// OFFER_DECLINED
{
userId: 'user-david',
type: 'OFFER_DECLINED' as const,
title: 'Offer Declined',
body: 'Carol Nguyen declined your offer of $200 for the Mid-Century Coffee Table.',
data: { offerId: 'offer-07', listingId: 'listing-02' },
isRead: true,
createdAt: daysAgo(5),
},
{
userId: 'user-eva',
type: 'OFFER_DECLINED' as const,
title: 'Offer Declined',
body: 'Bob Martinez declined your offer of $150 for the Sony WH-1000XM5.',
data: { offerId: 'offer-08', listingId: 'listing-08' },
isRead: false,
createdAt: daysAgo(8),
},
// ITEM_SOLD
{
userId: 'user-alice',
type: 'ITEM_SOLD' as const,
title: 'Item Sold!',
body: 'Your Nintendo Switch OLED has been sold for $275.',
data: { listingId: 'listing-11', amount: 275 },
isRead: true,
createdAt: daysAgo(9),
},
{
userId: 'user-carol',
type: 'ITEM_SOLD' as const,
title: 'Item Sold!',
body: 'Your Herman Miller Aeron Chair has been sold for $720.',
data: { listingId: 'listing-12', amount: 720 },
isRead: true,
createdAt: daysAgo(12),
},
// NEW_MESSAGE
{
userId: 'user-alice',
type: 'NEW_MESSAGE' as const,
title: 'New Message',
body: 'David Kim sent you a message about MacBook Pro 14" M3 Pro.',
data: { conversationId: 'conv-03', listingId: 'listing-01' },
isRead: true,
createdAt: daysAgo(3),
},
{
userId: 'user-david',
type: 'NEW_MESSAGE' as const,
title: 'New Message',
body: 'Alice Chen sent you a message about Weber Spirit II Gas Grill.',
data: { conversationId: 'conv-04', listingId: 'listing-04' },
isRead: false,
createdAt: daysAgo(2),
},
// ITEM_FAVORITED
{
userId: 'user-alice',
type: 'ITEM_FAVORITED' as const,
title: 'Item Favorited',
body: 'Someone favorited your MacBook Pro 14" M3 Pro listing.',
data: { listingId: 'listing-01' },
isRead: false,
createdAt: daysAgo(1),
},
{
userId: 'user-carol',
type: 'ITEM_FAVORITED' as const,
title: 'Item Favorited',
body: 'Someone favorited your Trek Marlin 7 Mountain Bike listing.',
data: { listingId: 'listing-05' },
isRead: true,
createdAt: daysAgo(4),
},
];
const notifications = await prisma.$transaction(
notificationsData.map((n) => prisma.notification.create({ data: n }))
);
console.log(`Created ${notifications.length} notifications.`);
// ── Payments (3 completed for sold items) ───────────────────────────
const paymentsData = [
{
userId: 'user-bob',
listingId: 'listing-11',
stripePaymentId: 'pi_3PxSwitch001',
amount: 275.00,
status: 'COMPLETED' as const,
createdAt: daysAgo(9),
},
{
userId: 'user-eva',
listingId: 'listing-12',
stripePaymentId: 'pi_3PxAeron002',
amount: 720.00,
status: 'COMPLETED' as const,
createdAt: daysAgo(12),
},
{
userId: 'user-alice',
listingId: 'listing-13',
stripePaymentId: 'pi_3PxCanon003',
amount: 1700.00,
status: 'COMPLETED' as const,
createdAt: daysAgo(6),
},
];
const payments = await prisma.$transaction(
paymentsData.map((p) => prisma.payment.create({ data: p }))
);
console.log(`Created ${payments.length} payments.`);
// ── Blocked Users ───────────────────────────────────────────────────
await prisma.blockedUser.create({
data: {
blockerId: 'user-carol',
blockedId: 'user-david',
},
});
console.log('Created 1 blocked user entry.');
// ── Platform Config ───────────────────────────────────────────────
await prisma.platformConfig.deleteMany();
await prisma.platformConfig.create({
data: {
listingFee: 5.00,
commissionPercent: 5.0,
autoApprove: true,
maxImagesPerListing: 6,
maxListingsFreeTier: 5,
proPrice: 9.99,
businessPrice: 29.99,
promotionDayPrice: 2.99,
blockedKeywords: ['illegal', 'drugs', 'weapons'],
rentalCommissionPercent: 10.0,
rentalAutoApprove: false,
maxRentalImagesPerListing: 10,
bookingExpiryHours: 48,
rentalPromotionDayPrice: 3.99,
},
});
console.log('Created PlatformConfig.');
// ── Assign roles to test users ────────────────────────────────────
await prisma.user.update({ where: { id: 'user-alice' }, data: { role: 'SUPER_ADMIN' } });
await prisma.user.update({ where: { id: 'user-bob' }, data: { role: 'ADMIN' } });
await prisma.user.update({ where: { id: 'user-carol' }, data: { role: 'MODERATOR' } });
console.log('Assigned roles: alice=SUPER_ADMIN, bob=ADMIN, carol=MODERATOR.');
// ── Rental Listings ─────────────────────────────────────────────────
// Make david a landlord
await prisma.user.update({ where: { id: 'user-david' }, data: { isLandlord: true, landlordVerified: true } });
// Make eva a landlord too
await prisma.user.update({ where: { id: 'user-eva' }, data: { isLandlord: true } });
const rentalListingsData = [
{
id: 'rental-01',
title: 'Modern Downtown Apartment — 2BR',
description: 'Spacious 2-bedroom apartment in the heart of downtown Chicago. Fully furnished with modern appliances, high-speed WiFi, and stunning city views from the 15th floor. Walking distance to restaurants, shops, and public transit.',
category: 'APARTMENT' as const,
location: 'Chicago, IL',
dailyPrice: 120.00,
monthlyPrice: 2800.00,
depositAmount: 500.00,
amenities: ['WiFi', 'Air Conditioning', 'Washer/Dryer', 'Parking', 'Gym', 'Elevator'],
rules: ['No smoking', 'No pets', 'Quiet hours 10PM-8AM'],
cancellationPolicy: 'MODERATE' as const,
minDays: 2,
maxDays: 90,
minMonths: 1,
maxMonths: 12,
status: 'ACTIVE' as const,
viewCount: 234,
isVerified: true,
landlordId: 'user-david',
},
{
id: 'rental-02',
title: 'Cozy Lake House with Private Dock',
description: 'Beautiful 3-bedroom lake house with private dock and boat access. Perfect for families or groups. Includes kayaks, paddleboards, and fishing equipment. Surrounded by nature with hiking trails nearby.',
category: 'HOUSE' as const,
location: 'Lake Geneva, WI',
dailyPrice: 250.00,
monthlyPrice: 5000.00,
depositAmount: 1000.00,
amenities: ['WiFi', 'Fireplace', 'BBQ', 'Boat Dock', 'Kayaks', 'Hot Tub'],
rules: ['No parties', 'No smoking indoors', 'Pets allowed with deposit'],
cancellationPolicy: 'STRICT' as const,
minDays: 3,
maxDays: 30,
status: 'ACTIVE' as const,
viewCount: 189,
landlordId: 'user-david',
},
{
id: 'rental-03',
title: 'Tesla Model 3 — Daily/Monthly Rental',
description: 'Clean 2024 Tesla Model 3 Long Range in Pearl White. Autopilot included. Full charge gives 350+ miles range. Insurance included in daily rate. Must be 25+ with valid license.',
category: 'CAR' as const,
location: 'Seattle, WA',
dailyPrice: 85.00,
monthlyPrice: 1800.00,
depositAmount: 500.00,
details: { year: 2024, make: 'Tesla', model: 'Model 3', color: 'Pearl White', mileage: 12000 },
amenities: ['Autopilot', 'Premium Audio', 'Heated Seats', 'Phone Charger'],
rules: ['No smoking', 'Must be 25+', 'Valid license required', 'Return fully charged'],
cancellationPolicy: 'FLEXIBLE' as const,
minDays: 1,
maxDays: 60,
status: 'ACTIVE' as const,
viewCount: 156,
landlordId: 'user-eva',
},
{
id: 'rental-04',
title: 'Harley-Davidson Sportster 883',
description: 'Classic Harley-Davidson Sportster 883 in Vivid Black. Perfect for weekend rides or road trips. Helmet included. Motorcycle license required.',
category: 'MOTORCYCLE' as const,
location: 'Portland, OR',
dailyPrice: 65.00,
depositAmount: 300.00,
amenities: ['Helmet Included', 'Saddlebags', 'Phone Mount'],
rules: ['Motorcycle license required', 'Must be 21+', 'No off-road use'],
cancellationPolicy: 'MODERATE' as const,
minDays: 1,
maxDays: 14,
status: 'ACTIVE' as const,
viewCount: 98,
landlordId: 'user-eva',
},
{
id: 'rental-05',
title: 'Trek City Bicycle — Daily Rental',
description: 'Comfortable Trek city bicycle perfect for exploring Portland. Includes lock, lights, and basket. Helmet available on request.',
category: 'BICYCLE' as const,
location: 'Portland, OR',
dailyPrice: 15.00,
amenities: ['Lock', 'Lights', 'Basket', 'Helmet Available'],
rules: ['Return by 8PM', 'Lock when unattended'],
cancellationPolicy: 'FLEXIBLE' as const,
minDays: 1,
maxDays: 7,
status: 'ACTIVE' as const,
viewCount: 67,
landlordId: 'user-eva',
},
{
id: 'rental-06',
title: 'VanMoof S5 Electric Bike',
description: 'Premium VanMoof S5 e-bike with boost button. Range up to 90 miles. Built-in anti-theft. Perfect for daily commuting or weekend exploring.',
category: 'EBIKE' as const,
location: 'San Francisco, CA',
dailyPrice: 35.00,
monthlyPrice: 600.00,
depositAmount: 200.00,
amenities: ['Anti-theft', 'Boost Button', 'Phone Mount', 'Lock'],
rules: ['Must wear helmet', 'Return charged', 'No off-road'],
cancellationPolicy: 'FLEXIBLE' as const,
minDays: 1,
maxDays: 30,
status: 'ACTIVE' as const,
viewCount: 112,
landlordId: 'user-david',
},
{
id: 'rental-07',
title: 'Luxury Penthouse — Pending Review',
description: 'Stunning penthouse apartment with panoramic views. 3 bedrooms, chef kitchen, private terrace. Under review.',
category: 'APARTMENT' as const,
location: 'Los Angeles, CA',
dailyPrice: 450.00,
monthlyPrice: 9000.00,
depositAmount: 2000.00,
amenities: ['Pool', 'Gym', 'Concierge', 'Parking', 'Terrace'],
rules: ['No parties', 'No smoking'],
cancellationPolicy: 'STRICT' as const,
status: 'PENDING_REVIEW' as const,
viewCount: 0,
landlordId: 'user-david',
},
];
const rentalListings = await prisma.$transaction(
rentalListingsData.map((r) => prisma.rentalListing.create({ data: r }))
);
console.log(`Created ${rentalListings.length} rental listings.`);
// ── Rental Images ─────────────────────────────────────────────────
const rentalImageRecords = [
{ rentalListingId: 'rental-01', url: '/uploads/rental-apartment-1.jpg', order: 0 },
{ rentalListingId: 'rental-01', url: '/uploads/rental-apartment-2.jpg', order: 1 },
{ rentalListingId: 'rental-01', url: '/uploads/rental-apartment-3.jpg', order: 2 },
{ rentalListingId: 'rental-02', url: '/uploads/rental-house-1.jpg', order: 0 },
{ rentalListingId: 'rental-02', url: '/uploads/rental-house-2.jpg', order: 1 },
{ rentalListingId: 'rental-02', url: '/uploads/rental-house-3.jpg', order: 2 },
{ rentalListingId: 'rental-03', url: '/uploads/rental-car-1.jpg', order: 0 },
{ rentalListingId: 'rental-03', url: '/uploads/rental-car-2.jpg', order: 1 },
{ rentalListingId: 'rental-04', url: '/uploads/rental-motorcycle-1.jpg', order: 0 },
{ rentalListingId: 'rental-04', url: '/uploads/rental-motorcycle-2.jpg', order: 1 },
{ rentalListingId: 'rental-05', url: '/uploads/rental-bicycle-1.jpg', order: 0 },
{ rentalListingId: 'rental-05', url: '/uploads/rental-bicycle-2.jpg', order: 1 },
{ rentalListingId: 'rental-06', url: '/uploads/rental-ebike-1.jpg', order: 0 },
{ rentalListingId: 'rental-06', url: '/uploads/rental-ebike-2.jpg', order: 1 },
{ rentalListingId: 'rental-07', url: '/uploads/rental-apartment-4.jpg', order: 0 },
];
await prisma.$transaction(
rentalImageRecords.map((img) => prisma.rentalImage.create({ data: img }))
);
console.log(`Created ${rentalImageRecords.length} rental images.`);
// ── Bookings ──────────────────────────────────────────────────────
const bookingsData = [
{
id: 'booking-01',
rentalListingId: 'rental-01',
tenantId: 'user-eva',
landlordId: 'user-david',
periodType: 'DAILY' as const,
startDate: daysAgo(-5),
endDate: daysAgo(-2),
pricePerPeriod: 120,
totalPeriods: 3,
subtotal: 360,
commissionRate: 10,
commissionAmount: 36,
depositAmount: 500,
totalAmount: 860,
status: 'COMPLETED' as const,
createdAt: daysAgo(10),
},
{
id: 'booking-02',
rentalListingId: 'rental-03',
tenantId: 'user-alice',
landlordId: 'user-eva',
periodType: 'DAILY' as const,
startDate: daysAgo(2),
endDate: daysAgo(-5),
pricePerPeriod: 85,
totalPeriods: 7,
subtotal: 595,
commissionRate: 10,
commissionAmount: 59.5,
depositAmount: 500,
totalAmount: 1095,
status: 'ACTIVE' as const,
createdAt: daysAgo(5),
},
{
id: 'booking-03',
rentalListingId: 'rental-02',
tenantId: 'user-bob',
landlordId: 'user-david',
periodType: 'DAILY' as const,
startDate: daysAgo(-10),
endDate: daysAgo(-7),
pricePerPeriod: 250,
totalPeriods: 3,
subtotal: 750,
commissionRate: 10,
commissionAmount: 75,
depositAmount: 1000,
totalAmount: 1750,
status: 'CONFIRMED' as const,
createdAt: daysAgo(3),
},
{
id: 'booking-04',
rentalListingId: 'rental-05',
tenantId: 'user-carol',
landlordId: 'user-eva',
periodType: 'DAILY' as const,
startDate: daysAgo(-14),
endDate: daysAgo(-12),
pricePerPeriod: 15,
totalPeriods: 2,
subtotal: 30,
commissionRate: 10,
commissionAmount: 3,
depositAmount: 0,
totalAmount: 30,
status: 'PENDING' as const,
expiresAt: daysAgo(-12),
createdAt: daysAgo(1),
},
];
const bookings = await prisma.$transaction(
bookingsData.map((b) => prisma.booking.create({ data: b }))
);
console.log(`Created ${bookings.length} bookings.`);
// ── Payouts for completed bookings ────────────────────────────────
await prisma.payout.create({
data: {
bookingId: 'booking-01',
landlordId: 'user-david',
grossAmount: 360,
commissionAmount: 36,
netAmount: 324,
status: 'COMPLETED',
},
});
console.log('Created 1 payout.');
// ── Rental Reviews ────────────────────────────────────────────────
await prisma.rentalReview.create({
data: {
bookingId: 'booking-01',
rentalListingId: 'rental-01',
tenantId: 'user-eva',
landlordId: 'user-david',
rating: 5,
comment: 'Amazing apartment! Super clean, great location, and David was an excellent host. The views were even better than the photos. Would definitely stay again.',
landlordResponse: 'Thank you Eva! You were a wonderful guest. Welcome back anytime!',
},
});
console.log('Created 1 rental review.');
// ── Rental Favorites ──────────────────────────────────────────────
await prisma.$transaction([
prisma.rentalFavorite.create({ data: { userId: 'user-alice', rentalListingId: 'rental-01' } }),
prisma.rentalFavorite.create({ data: { userId: 'user-alice', rentalListingId: 'rental-02' } }),
prisma.rentalFavorite.create({ data: { userId: 'user-bob', rentalListingId: 'rental-03' } }),
prisma.rentalFavorite.create({ data: { userId: 'user-carol', rentalListingId: 'rental-06' } }),
]);
console.log('Created 4 rental favorites.');
console.log('\nSeed completed successfully!');
}
main()
.catch((e) => {
console.error('Seed failed:', e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});