Files
marketplace/server/prisma/seed.ts
delta-lynx-89e8 d09c998d51 QA fixes: real listing creation, profile save, favorites, missing pages
- SellItemPage: real file upload + API listing creation + activate
- CreateProfilePage: save profile via PUT /users/profile
- ProductDetailPage: wire edit/delete/message buttons, show edit for owner
- ListingCard: persist favorites via API, show real images
- Footer: connect newsletter subscribe to API
- Router: add /dashboard/listings and /dashboard/saved routes
- Backend: add GET /listings/favorites endpoint
- New pages: MyListingsPage, SavedItemsPage
- Fix unused imports causing build failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:30:03 -08:00

794 lines
30 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.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.');
console.log('\nSeed completed successfully!');
}
main()
.catch((e) => {
console.error('Seed failed:', e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});