🔥 Scraper Improvements: - Add JSON-LD price extraction (regression fix) - Fix sleeping spotsPerUnit bug (was hardcoded to 2) - Remove stale CSS selectors, add robust fallbacks - Add JSON-LD price fallback in extraction pipeline - Improve sleeping parser regex (lastIndex bug fix) - Add 15+ new bed type patterns (murphy, day bed, hammock, plurals) - Smarter deriveSleepingFromBeds() with mixed bed logic 📅 Import Form UX: - Smart defaults (next weekend dates) - Auto-calculate nights display - URL param auto-detection (?check_in=&check_out=&adults=) - Better visual hierarchy with icons - Progress steps during import - Success redirect to listing detail page 🗑️ Delete Button Fix: - Add router.refresh() after successful delete - Inline error state instead of alert() - Admin delete button as proper client component ✏️ Edit/Admin Fixes: - Fix revalidatePath using slug instead of id - Fix redirect to detail page after edit - Add cascade delete logic to admin deleteListing - Extract delete to proper client component 🎨 UI States for Partial Data: - Price: 'Preis auf Anfrage' with context hint - Location: 'Ort nicht erkannt' instead of empty - Sleeping: placeholder when no data - Suitability: 3-state (yes/no/unknown) - Use formatPrice/formatRating utilities 🛏️ Sleeping Data Quality: - Add sleepingDataQuality to Prisma schema - Save quality (EXACT/DERIVED/UNKNOWN) to DB - Display '(geschätzt)' label for derived data 📊 Database: - Restore corrupted schema.prisma from git - Add sleepingDataQuality field - Push schema changes ✅ TypeScript: Zero errors ✅ Build: Successful
157 lines
4.9 KiB
Plaintext
157 lines
4.9 KiB
Plaintext
// This is your Prisma schema file
|
|
// Learn more: https://pris.ly/d/prisma-schema
|
|
|
|
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "sqlite"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
// ============================================
|
|
// MODELS
|
|
// ============================================
|
|
|
|
model Listing {
|
|
id String @id @default(cuid())
|
|
slug String @unique
|
|
airbnbUrl String @unique @map("airbnb_url")
|
|
normalizedUrl String @unique @map("normalized_url")
|
|
externalId String? @unique @map("external_id")
|
|
|
|
// Basic Info
|
|
title String
|
|
locationText String? @map("location_text")
|
|
latitude Float?
|
|
longitude Float?
|
|
|
|
// Pricing
|
|
nightlyPrice Float? @map("nightly_price")
|
|
totalPrice Float? @map("total_price")
|
|
currency String? @default("EUR")
|
|
priceStatus String? @map("price_status") // EXTRACTED, REQUIRES_TRIP_CONTEXT, UNKNOWN, PARTIAL
|
|
|
|
// Rating
|
|
rating Float?
|
|
reviewCount Int? @map("review_count")
|
|
|
|
// Capacity
|
|
guestCount Int? @map("guest_count")
|
|
officialGuestCount Int? @map("official_guest_count")
|
|
|
|
// Sleeping Analysis
|
|
maxSleepingPlaces Int? @map("max_sleeping_places")
|
|
suitableFor4 Boolean? @map("suitable_for_4")
|
|
extraMattressesNeededFor4 Int? @map("extra_mattresses_needed_for_4")
|
|
bedTypesSummary String? @map("bed_types_summary")
|
|
sleepingDataQuality String? @map("sleeping_data_quality") // EXACT, DERIVED, UNKNOWN
|
|
|
|
// Room Details
|
|
bedrooms Int?
|
|
beds Int?
|
|
bathrooms Float?
|
|
|
|
// Description
|
|
description String?
|
|
hostName String? @map("host_name")
|
|
cancellationPolicy String? @map("cancellation_policy")
|
|
|
|
// Amenities (stored as JSON string)
|
|
amenities String? @map("amenities")
|
|
|
|
// Status & Flags (using String instead of Enum for SQLite)
|
|
isFavorite Boolean @default(false) @map("is_favorite")
|
|
status String @default("NEW") // NEW, INTERESTING, SHORTLIST, BOOKED, REJECTED
|
|
|
|
// Images
|
|
coverImage String? @map("cover_image")
|
|
|
|
// Raw Data
|
|
rawSourceData String? @map("raw_source_data")
|
|
|
|
// Timestamps
|
|
importedAt DateTime @default(now()) @map("imported_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
// Relations
|
|
images ListingImage[]
|
|
notes AdminNote[]
|
|
sleepingOptions ListingSleepingOption[]
|
|
tags ListingTag[]
|
|
|
|
// Indexes
|
|
@@index([status])
|
|
@@index([isFavorite])
|
|
@@index([locationText])
|
|
@@index([nightlyPrice])
|
|
@@index([rating])
|
|
@@map("listings")
|
|
}
|
|
|
|
model ListingImage {
|
|
id String @id @default(cuid())
|
|
listingId String @map("listing_id")
|
|
url String
|
|
alt String?
|
|
sortOrder Int @default(0) @map("sort_order")
|
|
isExternal Boolean @default(true) @map("is_external")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
listing Listing @relation(fields: [listingId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([listingId, sortOrder])
|
|
@@map("listing_images")
|
|
}
|
|
|
|
model AdminNote {
|
|
id String @id @default(cuid())
|
|
listingId String @map("listing_id")
|
|
body String
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
listing Listing @relation(fields: [listingId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([listingId, createdAt])
|
|
@@map("admin_notes")
|
|
}
|
|
|
|
model Tag {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
slug String @unique
|
|
color String? @default("#6366f1")
|
|
listings ListingTag[]
|
|
|
|
@@map("tags")
|
|
}
|
|
|
|
model ListingTag {
|
|
listingId String @map("listing_id")
|
|
tagId String @map("tag_id")
|
|
|
|
listing Listing @relation(fields: [listingId], references: [id], onDelete: Cascade)
|
|
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
|
|
|
|
@@id([listingId, tagId])
|
|
@@map("listing_tags")
|
|
}
|
|
|
|
model ListingSleepingOption {
|
|
id String @id @default(cuid())
|
|
listingId String @map("listing_id")
|
|
bedType String @map("bed_type") // DOUBLE, SINGLE, SOFA_BED, etc.
|
|
quantity Int @default(1)
|
|
spotsPerUnit Int @default(1) @map("spots_per_unit")
|
|
quality String @default("FULL") // FULL, AUXILIARY
|
|
label String?
|
|
notes String?
|
|
|
|
listing Listing @relation(fields: [listingId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([listingId])
|
|
@@map("listing_sleeping_options")
|
|
}
|