- 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>
794 lines
30 KiB
TypeScript
794 lines
30 KiB
TypeScript
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();
|
||
});
|