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:
@@ -0,0 +1,336 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "RentalCategory" AS ENUM ('APARTMENT', 'HOUSE', 'CAR', 'MOTORCYCLE', 'BICYCLE', 'EBIKE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "RentalPeriodType" AS ENUM ('DAILY', 'MONTHLY');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "RentalListingStatus" AS ENUM ('DRAFT', 'PENDING_REVIEW', 'ACTIVE', 'PAUSED', 'DELETED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "BookingStatus" AS ENUM ('PENDING', 'CONFIRMED', 'ACTIVE', 'COMPLETED', 'CANCELLED_BY_TENANT', 'CANCELLED_BY_LANDLORD', 'REJECTED', 'EXPIRED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PayoutStatus" AS ENUM ('PENDING', 'PROCESSING', 'COMPLETED', 'FAILED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "CancellationPolicy" AS ENUM ('FLEXIBLE', 'MODERATE', 'STRICT');
|
||||
|
||||
-- AlterEnum
|
||||
-- This migration adds more than one value to an enum.
|
||||
-- With PostgreSQL versions 11 and earlier, this is not possible
|
||||
-- in a single migration. This can be worked around by creating
|
||||
-- multiple migrations, each migration adding only one value to
|
||||
-- the enum.
|
||||
|
||||
|
||||
ALTER TYPE "NotificationType" ADD VALUE 'BOOKING_REQUEST';
|
||||
ALTER TYPE "NotificationType" ADD VALUE 'BOOKING_CONFIRMED';
|
||||
ALTER TYPE "NotificationType" ADD VALUE 'BOOKING_REJECTED';
|
||||
ALTER TYPE "NotificationType" ADD VALUE 'BOOKING_CANCELLED';
|
||||
ALTER TYPE "NotificationType" ADD VALUE 'BOOKING_STARTED';
|
||||
ALTER TYPE "NotificationType" ADD VALUE 'BOOKING_COMPLETED';
|
||||
ALTER TYPE "NotificationType" ADD VALUE 'RENTAL_REVIEW';
|
||||
ALTER TYPE "NotificationType" ADD VALUE 'PAYOUT_SENT';
|
||||
ALTER TYPE "NotificationType" ADD VALUE 'PAYOUT_FAILED';
|
||||
|
||||
-- AlterEnum
|
||||
-- This migration adds more than one value to an enum.
|
||||
-- With PostgreSQL versions 11 and earlier, this is not possible
|
||||
-- in a single migration. This can be worked around by creating
|
||||
-- multiple migrations, each migration adding only one value to
|
||||
-- the enum.
|
||||
|
||||
|
||||
ALTER TYPE "PaymentType" ADD VALUE 'RENTAL_BOOKING';
|
||||
ALTER TYPE "PaymentType" ADD VALUE 'RENTAL_COMMISSION';
|
||||
ALTER TYPE "PaymentType" ADD VALUE 'RENTAL_DEPOSIT';
|
||||
ALTER TYPE "PaymentType" ADD VALUE 'RENTAL_DEPOSIT_REFUND';
|
||||
ALTER TYPE "PaymentType" ADD VALUE 'RENTAL_PAYOUT';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Conversation" ADD COLUMN "rentalListingId" TEXT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "PlatformConfig" ADD COLUMN "bookingExpiryHours" INTEGER NOT NULL DEFAULT 48,
|
||||
ADD COLUMN "maxRentalImagesPerListing" INTEGER NOT NULL DEFAULT 10,
|
||||
ADD COLUMN "rentalAutoApprove" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "rentalCommissionPercent" DOUBLE PRECISION NOT NULL DEFAULT 10.0,
|
||||
ADD COLUMN "rentalPromotionDayPrice" DOUBLE PRECISION NOT NULL DEFAULT 3.99;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "isLandlord" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "landlordVerified" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "stripeAccountId" TEXT;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "RentalListing" (
|
||||
"id" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"category" "RentalCategory" NOT NULL,
|
||||
"location" TEXT NOT NULL,
|
||||
"dailyPrice" DOUBLE PRECISION,
|
||||
"monthlyPrice" DOUBLE PRECISION,
|
||||
"depositAmount" DOUBLE PRECISION,
|
||||
"details" JSONB,
|
||||
"amenities" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"rules" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"cancellationPolicy" "CancellationPolicy" NOT NULL DEFAULT 'FLEXIBLE',
|
||||
"minDays" INTEGER,
|
||||
"maxDays" INTEGER,
|
||||
"minMonths" INTEGER,
|
||||
"maxMonths" INTEGER,
|
||||
"status" "RentalListingStatus" NOT NULL DEFAULT 'DRAFT',
|
||||
"viewCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"isFeatured" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isVerified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"rejectionReason" TEXT,
|
||||
"reviewedBy" TEXT,
|
||||
"reviewedAt" TIMESTAMP(3),
|
||||
"landlordId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "RentalListing_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "RentalImage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"url" TEXT NOT NULL,
|
||||
"order" INTEGER NOT NULL DEFAULT 0,
|
||||
"rentalListingId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "RentalImage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AvailabilityBlock" (
|
||||
"id" TEXT NOT NULL,
|
||||
"rentalListingId" TEXT NOT NULL,
|
||||
"startDate" TIMESTAMP(3) NOT NULL,
|
||||
"endDate" TIMESTAMP(3) NOT NULL,
|
||||
"isBlocked" BOOLEAN NOT NULL DEFAULT true,
|
||||
"reason" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "AvailabilityBlock_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Booking" (
|
||||
"id" TEXT NOT NULL,
|
||||
"rentalListingId" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"landlordId" TEXT NOT NULL,
|
||||
"periodType" "RentalPeriodType" NOT NULL,
|
||||
"startDate" TIMESTAMP(3) NOT NULL,
|
||||
"endDate" TIMESTAMP(3) NOT NULL,
|
||||
"pricePerPeriod" DOUBLE PRECISION NOT NULL,
|
||||
"totalPeriods" INTEGER NOT NULL,
|
||||
"subtotal" DOUBLE PRECISION NOT NULL,
|
||||
"commissionRate" DOUBLE PRECISION NOT NULL DEFAULT 10.0,
|
||||
"commissionAmount" DOUBLE PRECISION NOT NULL,
|
||||
"depositAmount" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"totalAmount" DOUBLE PRECISION NOT NULL,
|
||||
"status" "BookingStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"message" TEXT,
|
||||
"rejectionReason" TEXT,
|
||||
"cancellationReason" TEXT,
|
||||
"stripePaymentIntentId" TEXT,
|
||||
"expiresAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Booking_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "BookingPayment" (
|
||||
"id" TEXT NOT NULL,
|
||||
"bookingId" TEXT NOT NULL,
|
||||
"stripePaymentId" TEXT,
|
||||
"amount" DOUBLE PRECISION NOT NULL,
|
||||
"type" "PaymentType" NOT NULL,
|
||||
"status" "PaymentStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "BookingPayment_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Payout" (
|
||||
"id" TEXT NOT NULL,
|
||||
"bookingId" TEXT NOT NULL,
|
||||
"landlordId" TEXT NOT NULL,
|
||||
"grossAmount" DOUBLE PRECISION NOT NULL,
|
||||
"commissionAmount" DOUBLE PRECISION NOT NULL,
|
||||
"netAmount" DOUBLE PRECISION NOT NULL,
|
||||
"status" "PayoutStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"stripeTransferId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Payout_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "RentalReview" (
|
||||
"id" TEXT NOT NULL,
|
||||
"bookingId" TEXT NOT NULL,
|
||||
"rentalListingId" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"landlordId" TEXT NOT NULL,
|
||||
"rating" INTEGER NOT NULL,
|
||||
"comment" TEXT,
|
||||
"landlordResponse" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "RentalReview_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "RentalFavorite" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"rentalListingId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "RentalFavorite_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PromotedRental" (
|
||||
"id" TEXT NOT NULL,
|
||||
"rentalListingId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"startDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"endDate" TIMESTAMP(3) NOT NULL,
|
||||
"amountPaid" DOUBLE PRECISION NOT NULL,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "PromotedRental_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RentalListing_landlordId_idx" ON "RentalListing"("landlordId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RentalListing_category_idx" ON "RentalListing"("category");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RentalListing_status_idx" ON "RentalListing"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RentalListing_createdAt_idx" ON "RentalListing"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RentalImage_rentalListingId_idx" ON "RentalImage"("rentalListingId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AvailabilityBlock_rentalListingId_idx" ON "AvailabilityBlock"("rentalListingId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AvailabilityBlock_startDate_endDate_idx" ON "AvailabilityBlock"("startDate", "endDate");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Booking_rentalListingId_idx" ON "Booking"("rentalListingId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Booking_tenantId_idx" ON "Booking"("tenantId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Booking_landlordId_idx" ON "Booking"("landlordId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Booking_status_idx" ON "Booking"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "BookingPayment_stripePaymentId_key" ON "BookingPayment"("stripePaymentId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "BookingPayment_bookingId_idx" ON "BookingPayment"("bookingId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Payout_bookingId_key" ON "Payout"("bookingId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Payout_landlordId_idx" ON "Payout"("landlordId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Payout_status_idx" ON "Payout"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "RentalReview_bookingId_key" ON "RentalReview"("bookingId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RentalReview_rentalListingId_idx" ON "RentalReview"("rentalListingId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RentalReview_landlordId_idx" ON "RentalReview"("landlordId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "RentalFavorite_userId_rentalListingId_key" ON "RentalFavorite"("userId", "rentalListingId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "PromotedRental_rentalListingId_key" ON "PromotedRental"("rentalListingId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PromotedRental_isActive_endDate_idx" ON "PromotedRental"("isActive", "endDate");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Conversation" ADD CONSTRAINT "Conversation_rentalListingId_fkey" FOREIGN KEY ("rentalListingId") REFERENCES "RentalListing"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RentalListing" ADD CONSTRAINT "RentalListing_landlordId_fkey" FOREIGN KEY ("landlordId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RentalImage" ADD CONSTRAINT "RentalImage_rentalListingId_fkey" FOREIGN KEY ("rentalListingId") REFERENCES "RentalListing"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AvailabilityBlock" ADD CONSTRAINT "AvailabilityBlock_rentalListingId_fkey" FOREIGN KEY ("rentalListingId") REFERENCES "RentalListing"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Booking" ADD CONSTRAINT "Booking_rentalListingId_fkey" FOREIGN KEY ("rentalListingId") REFERENCES "RentalListing"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Booking" ADD CONSTRAINT "Booking_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Booking" ADD CONSTRAINT "Booking_landlordId_fkey" FOREIGN KEY ("landlordId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BookingPayment" ADD CONSTRAINT "BookingPayment_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Payout" ADD CONSTRAINT "Payout_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Payout" ADD CONSTRAINT "Payout_landlordId_fkey" FOREIGN KEY ("landlordId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RentalReview" ADD CONSTRAINT "RentalReview_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RentalReview" ADD CONSTRAINT "RentalReview_rentalListingId_fkey" FOREIGN KEY ("rentalListingId") REFERENCES "RentalListing"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RentalReview" ADD CONSTRAINT "RentalReview_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RentalReview" ADD CONSTRAINT "RentalReview_landlordId_fkey" FOREIGN KEY ("landlordId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RentalFavorite" ADD CONSTRAINT "RentalFavorite_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RentalFavorite" ADD CONSTRAINT "RentalFavorite_rentalListingId_fkey" FOREIGN KEY ("rentalListingId") REFERENCES "RentalListing"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PromotedRental" ADD CONSTRAINT "PromotedRental_rentalListingId_fkey" FOREIGN KEY ("rentalListingId") REFERENCES "RentalListing"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PromotedRental" ADD CONSTRAINT "PromotedRental_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
Reference in New Issue
Block a user