Add rental system: listings, bookings, payments, payouts, reviews

Full rental marketplace with 6 categories (apartment, house, car, motorcycle, bicycle, ebike).
Booking workflow: create → confirm → pay → active → complete → payout.
Landlord dashboard, admin moderation, availability calendar, Stripe Connect payouts.
14 QA bugs found and fixed including validator schemas, API response types, HTTP methods.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
delta-lynx-89e8
2026-02-22 15:33:29 -08:00
parent 8961fa701a
commit dbbbbd26f4
90 changed files with 11052 additions and 124 deletions

View File

@@ -11,6 +11,20 @@ async function main() {
// ── 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(),
@@ -780,6 +794,310 @@ async function main() {
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/placeholder-r1.jpg', order: 0 },
{ rentalListingId: 'rental-01', url: '/uploads/placeholder-r2.jpg', order: 1 },
{ rentalListingId: 'rental-02', url: '/uploads/placeholder-r3.jpg', order: 0 },
{ rentalListingId: 'rental-02', url: '/uploads/placeholder-r4.jpg', order: 1 },
{ rentalListingId: 'rental-03', url: '/uploads/placeholder-r5.jpg', order: 0 },
{ rentalListingId: 'rental-04', url: '/uploads/placeholder-r6.jpg', order: 0 },
{ rentalListingId: 'rental-05', url: '/uploads/placeholder-r7.jpg', order: 0 },
{ rentalListingId: 'rental-06', url: '/uploads/placeholder-r8.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!');
}