first commit
This commit is contained in:
+675
@@ -0,0 +1,675 @@
|
||||
import { db } from './db'
|
||||
|
||||
export interface Category {
|
||||
id: string
|
||||
slug: string
|
||||
nameTr: string
|
||||
nameEn: string
|
||||
nameRu: string
|
||||
}
|
||||
|
||||
export interface Neighborhood {
|
||||
id: string
|
||||
slug: string
|
||||
nameTr: string
|
||||
nameEn: string
|
||||
nameRu: string
|
||||
}
|
||||
|
||||
export interface Gallery {
|
||||
id: string
|
||||
listingId: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export interface Listing {
|
||||
id: string
|
||||
slug: string
|
||||
categoryId: string
|
||||
neighborhoodId: string
|
||||
city: string
|
||||
nameTr: string
|
||||
nameEn: string
|
||||
nameRu: string
|
||||
descriptionTr: string
|
||||
descriptionEn: string
|
||||
descriptionRu: string
|
||||
address: string
|
||||
phone?: string | null
|
||||
whatsapp?: string | null
|
||||
website?: string | null
|
||||
instagram?: string | null
|
||||
priceRange: number
|
||||
rating?: number | null
|
||||
isLocalApproved: boolean
|
||||
latitude?: number | null
|
||||
longitude?: number | null
|
||||
openingHours?: any | null
|
||||
images: Gallery[]
|
||||
category?: Category
|
||||
neighborhood?: Neighborhood
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
deletedAt?: Date | null
|
||||
}
|
||||
|
||||
export interface BusinessSubmission {
|
||||
id: string
|
||||
businessName: string
|
||||
categoryId: string
|
||||
neighborhoodId: string
|
||||
address: string
|
||||
phone?: string | null
|
||||
whatsapp?: string | null
|
||||
description: string
|
||||
contactName: string
|
||||
contactEmail: string
|
||||
imageUrl?: string | null
|
||||
status: string
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
export interface ContactMessage {
|
||||
id: string
|
||||
name: string
|
||||
email: string
|
||||
subject: string
|
||||
message: string
|
||||
isRead: boolean
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
const globalForMockDb = globalThis as unknown as {
|
||||
categories: Category[]
|
||||
neighborhoods: Neighborhood[]
|
||||
listings: Listing[]
|
||||
submissions: BusinessSubmission[]
|
||||
messages: ContactMessage[]
|
||||
initialized: boolean
|
||||
}
|
||||
|
||||
if (!globalForMockDb.initialized) {
|
||||
globalForMockDb.categories = [
|
||||
{ id: 'cat-1', slug: 'restoran', nameTr: 'Restoran', nameEn: 'Restaurant', nameRu: 'Ресторан' },
|
||||
{ id: 'cat-2', slug: 'apart', nameTr: 'Apart', nameEn: 'Apart Hotel', nameRu: 'Апарт-отель' },
|
||||
{ id: 'cat-3', slug: 'isletme', nameTr: 'İşletme & Hizmet', nameEn: 'Business & Service', nameRu: 'Бизнес и Услуги' }
|
||||
]
|
||||
|
||||
globalForMockDb.neighborhoods = [
|
||||
{ id: 'neigh-1', slug: 'yat-limani', nameTr: 'Yat Limanı', nameEn: 'Marina', nameRu: 'Марина' },
|
||||
{ id: 'neigh-2', slug: 'icmeler', nameTr: 'İçmeler', nameEn: 'Icmeler', nameRu: 'Ичмелер' },
|
||||
{ id: 'neigh-3', slug: 'armutalan', nameTr: 'Armutalan', nameEn: 'Armutalan', nameRu: 'Армуталан' },
|
||||
{ id: 'neigh-4', slug: 'siteler', nameTr: 'Siteler', nameEn: 'Siteler', nameRu: 'Сителер' },
|
||||
{ id: 'neigh-5', slug: 'turunc', nameTr: 'Turunç', nameEn: 'Turunc', nameRu: 'Турунч' }
|
||||
]
|
||||
|
||||
globalForMockDb.submissions = []
|
||||
globalForMockDb.messages = []
|
||||
|
||||
// Pre-populate with realistic Marmaris data
|
||||
globalForMockDb.listings = [
|
||||
{
|
||||
id: 'list-1',
|
||||
slug: 'iskele-balik-ocakbasi',
|
||||
categoryId: 'cat-1',
|
||||
neighborhoodId: 'neigh-1',
|
||||
city: 'marmaris',
|
||||
nameTr: 'İskele Balık Ocakbaşı',
|
||||
nameEn: 'Iskele Fish & Grill',
|
||||
nameRu: 'Рыбный Гриль Искеле',
|
||||
descriptionTr: 'Yat Limanı\'nda taze Ege balıkları ve geleneksel meze çeşitleriyle yerel lezzet durağınız. Mükemmel körfez manzarası eşliğinde akşam yemeği.',
|
||||
descriptionEn: 'Your local taste stop at the Marina with fresh Aegean fish and traditional appetizers. Dinner accompanied by excellent bay views.',
|
||||
descriptionRu: 'Ваша местная гастрономическая остановка в Марине со свежей эгейской рыбой и традиционными закусками. Ужин в сопровождении великолепного вида на залив.',
|
||||
address: 'Yat Limanı No:12, Marmaris',
|
||||
phone: '+90 252 412 34 56',
|
||||
whatsapp: '+90 532 123 45 67',
|
||||
website: 'https://iskelemarmaris.com',
|
||||
instagram: 'https://instagram.com/iskele_marmaris',
|
||||
priceRange: 3,
|
||||
rating: 4.8,
|
||||
isLocalApproved: true,
|
||||
latitude: 36.8524,
|
||||
longitude: 28.2741,
|
||||
openingHours: { all: '12:00 - 00:00' },
|
||||
images: [
|
||||
{ id: 'img-1-1', listingId: 'list-1', url: 'https://images.unsplash.com/photo-1519708227418-c8fd9a32b7a2?w=800&auto=format&fit=crop&q=80' },
|
||||
{ id: 'img-1-2', listingId: 'list-1', url: 'https://images.unsplash.com/photo-1555396273-367ea4eb4db5?w=800&auto=format&fit=crop&q=80' }
|
||||
],
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
id: 'list-2',
|
||||
slug: 'mavi-beyaz-restoran',
|
||||
categoryId: 'cat-1',
|
||||
neighborhoodId: 'neigh-1',
|
||||
city: 'marmaris',
|
||||
nameTr: 'Mavi Beyaz Restoran',
|
||||
nameEn: 'Blue White Restaurant',
|
||||
nameRu: 'Ресторан Сине-Белый',
|
||||
descriptionTr: 'Ege kıyılarının esintisini taşıyan, deniz ürünleri ağırlıklı menüsü ve gün batımı eşliğindeki eşsiz mezeleriyle ünlüdür.',
|
||||
descriptionEn: 'Famous for its seafood-oriented menu carrying the breeze of the Aegean coasts and unique appetizers accompanied by sunset.',
|
||||
descriptionRu: 'Славится своим меню из морепродуктов, несущим бриз Эгейского побережья, и уникальными закусками на фоне заката.',
|
||||
address: 'Kordon Caddesi No:45, Yat Limanı, Marmaris',
|
||||
phone: '+90 252 412 78 90',
|
||||
whatsapp: '+90 533 987 65 43',
|
||||
website: 'https://mavibeyazmarmaris.com',
|
||||
instagram: 'https://instagram.com/mavibeyaz_marmaris',
|
||||
priceRange: 2,
|
||||
rating: 4.5,
|
||||
isLocalApproved: true,
|
||||
latitude: 36.8530,
|
||||
longitude: 28.2725,
|
||||
openingHours: { all: '11:00 - 23:30' },
|
||||
images: [
|
||||
{ id: 'img-2-1', listingId: 'list-2', url: 'https://images.unsplash.com/photo-1414235077428-338989a2e8c0?w=800&auto=format&fit=crop&q=80' }
|
||||
],
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
id: 'list-3',
|
||||
slug: 'dostlar-kebap',
|
||||
categoryId: 'cat-1',
|
||||
neighborhoodId: 'neigh-3',
|
||||
city: 'marmaris',
|
||||
nameTr: 'Dostlar Kebap',
|
||||
nameEn: 'Dostlar Kebab House',
|
||||
nameRu: 'Кебаб Хаус Достлар',
|
||||
descriptionTr: 'Marmaris Armutalan\'da yıllardır değişmeyen lezzetiyle gerçek ocakbaşı ve kebap deneyimi sunan samimi bir mahalle restoranı.',
|
||||
descriptionEn: 'A cozy neighborhood restaurant offering real grill and kebab experience with its unchanged taste for years in Armutalan, Marmaris.',
|
||||
descriptionRu: 'Уютный районный ресторан, предлагающий настоящий гриль и кебаб с неизменным вкусом на протяжении многих лет в Армуталане, Мармарис.',
|
||||
address: 'Vatan Caddesi No:18, Armutalan, Marmaris',
|
||||
phone: '+90 252 413 11 22',
|
||||
whatsapp: null,
|
||||
website: null,
|
||||
instagram: 'https://instagram.com/dostlarkebap_marmaris',
|
||||
priceRange: 1,
|
||||
rating: 4.7,
|
||||
isLocalApproved: false,
|
||||
latitude: 36.8488,
|
||||
longitude: 28.2450,
|
||||
openingHours: { all: '11:00 - 22:00' },
|
||||
images: [
|
||||
{ id: 'img-3-1', listingId: 'list-3', url: 'https://images.unsplash.com/photo-1544025162-d76694265947?w=800&auto=format&fit=crop&q=80' }
|
||||
],
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
id: 'list-4',
|
||||
slug: 'zeytin-bahcesi-apart',
|
||||
categoryId: 'cat-2',
|
||||
neighborhoodId: 'neigh-3',
|
||||
city: 'marmaris',
|
||||
nameTr: 'Zeytin Bahçesi Apart',
|
||||
nameEn: 'Olive Garden Apart',
|
||||
nameRu: 'Апарт Оливковый Сад',
|
||||
descriptionTr: 'Zeytin ağaçları arasında, sakin ve huzurlu bir ortamda ailece tatil yapmak isteyenler için tasarlanmış geniş mutfaklı apart daireler.',
|
||||
descriptionEn: 'Spacious apart apartments with kitchens designed for families who want to have a holiday in a quiet and peaceful environment among olive trees.',
|
||||
descriptionRu: 'Просторные апартаменты с кухнями, предназначенные для семей, желающих отдохнуть в тихой и спокойной обстановке среди оливковых деревьев.',
|
||||
address: 'Zeytinlik Sokak No:5, Armutalan, Marmaris',
|
||||
phone: '+90 252 413 55 66',
|
||||
whatsapp: '+90 535 555 66 77',
|
||||
website: 'https://zeytinbahcesiapart.com',
|
||||
instagram: null,
|
||||
priceRange: 2,
|
||||
rating: 4.6,
|
||||
isLocalApproved: true,
|
||||
latitude: 36.8510,
|
||||
longitude: 28.2415,
|
||||
openingHours: null,
|
||||
images: [
|
||||
{ id: 'img-4-1', listingId: 'list-4', url: 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=800&auto=format&fit=crop&q=80' },
|
||||
{ id: 'img-4-2', listingId: 'list-4', url: 'https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?w=800&auto=format&fit=crop&q=80' }
|
||||
],
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
id: 'list-5',
|
||||
slug: 'deniz-apart',
|
||||
categoryId: 'cat-2',
|
||||
neighborhoodId: 'neigh-4',
|
||||
city: 'marmaris',
|
||||
nameTr: 'Deniz Apart',
|
||||
nameEn: 'Sea Apart Hotel',
|
||||
nameRu: 'Апарт-отель Дениз',
|
||||
descriptionTr: 'Denize sadece 100 metre mesafede, bütçe dostu fiyatları ve güler yüzlü yerel işletme sahibiyle Marmaris\'te sıcak konaklama.',
|
||||
descriptionEn: 'Cozy accommodation in Marmaris, just 100 meters from the sea, with budget-friendly prices and a friendly local owner.',
|
||||
descriptionRu: 'Уютное жилье в Мармарисе, всего в 100 метрах от моря, с доступными ценами и дружелюбным местным владельцем.',
|
||||
address: 'Sahil Yolu Caddesi No:88, Siteler, Marmaris',
|
||||
phone: '+90 252 417 88 99',
|
||||
whatsapp: null,
|
||||
website: null,
|
||||
instagram: null,
|
||||
priceRange: 1,
|
||||
rating: 4.2,
|
||||
isLocalApproved: false,
|
||||
latitude: 36.8375,
|
||||
longitude: 28.2580,
|
||||
openingHours: null,
|
||||
images: [
|
||||
{ id: 'img-5-1', listingId: 'list-5', url: 'https://images.unsplash.com/photo-1582719478250-c89cae4dc85b?w=800&auto=format&fit=crop&q=80' }
|
||||
],
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
id: 'list-6',
|
||||
slug: 'marina-dalis-merkezi',
|
||||
categoryId: 'cat-3',
|
||||
neighborhoodId: 'neigh-2',
|
||||
city: 'marmaris',
|
||||
nameTr: 'Marina Dalış Merkezi',
|
||||
nameEn: 'Marina Diving Center',
|
||||
nameRu: 'Дайвинг-центр Марина',
|
||||
descriptionTr: 'Marmaris\'in eşsiz koylarında profesyonel eğitmenler eşliğinde tüplü dalış deneyimi. Başlangıç seviyesinden PADI sertifikasyonuna kadar hizmet.',
|
||||
descriptionEn: 'Scuba diving experience in unique bays of Marmaris accompanied by professional instructors. Service from discovery dive to PADI certification.',
|
||||
descriptionRu: 'Опыт подводного плавания в уникальных бухтах Мармариса в сопровождении профессиональных инструкторов. Услуги от ознакомительного погружения до сертификации PADI.',
|
||||
address: 'Liman Yolu No:32, İçmeler, Marmaris',
|
||||
phone: '+90 252 455 22 33',
|
||||
whatsapp: '+90 536 777 88 99',
|
||||
website: 'https://marinadivingmarmaris.com',
|
||||
instagram: 'https://instagram.com/marinadiving_marmaris',
|
||||
priceRange: 3,
|
||||
rating: 4.9,
|
||||
isLocalApproved: true,
|
||||
latitude: 36.8020,
|
||||
longitude: 28.2320,
|
||||
openingHours: { all: '08:30 - 19:30' },
|
||||
images: [
|
||||
{ id: 'img-6-1', listingId: 'list-6', url: 'https://images.unsplash.com/photo-1544551763-46a013bb70d5?w=800&auto=format&fit=crop&q=80' }
|
||||
],
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
id: 'list-7',
|
||||
slug: 'ege-ruzgari-tekne-kiralama',
|
||||
categoryId: 'cat-3',
|
||||
neighborhoodId: 'neigh-1',
|
||||
city: 'marmaris',
|
||||
nameTr: 'Ege Rüzgarı Tekne Kiralama',
|
||||
nameEn: 'Aegean Wind Boat Rental',
|
||||
nameRu: 'Аренда Лодок Эгейский Ветер',
|
||||
descriptionTr: 'Kaptanlı veya kaptansız günlük ve haftalık özel tekne kiralama hizmeti. Marmaris koylarını kendi rotanızda özgürce keşfedin.',
|
||||
descriptionEn: 'Daily and weekly private boat rental service with or without skipper. Discover Marmaris bays freely on your own route.',
|
||||
descriptionRu: 'Ежедневная и еженедельная аренда частных лодок со шкипером или без. Откройте для себя бухты Мармариса свободно по собственному маршруту.',
|
||||
address: 'Yat Limanı G İskelesi, Marmaris',
|
||||
phone: '+90 532 999 88 77',
|
||||
whatsapp: '+90 532 999 88 77',
|
||||
website: 'https://egeruzgariboat.com',
|
||||
instagram: 'https://instagram.com/egeruzgariboat',
|
||||
priceRange: 3,
|
||||
rating: 4.8,
|
||||
isLocalApproved: true,
|
||||
latitude: 36.8528,
|
||||
longitude: 28.2755,
|
||||
openingHours: { all: '08:00 - 21:00' },
|
||||
images: [
|
||||
{ id: 'img-7-1', listingId: 'list-7', url: 'https://images.unsplash.com/photo-1567899378494-47b22a2ae96a?w=800&auto=format&fit=crop&q=80' }
|
||||
],
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
id: 'list-8',
|
||||
slug: 'marmaris-transfer-tours',
|
||||
categoryId: 'cat-3',
|
||||
neighborhoodId: 'neigh-3',
|
||||
city: 'marmaris',
|
||||
nameTr: 'Marmaris VIP Transfer & Tur',
|
||||
nameEn: 'Marmaris VIP Transfer & Tours',
|
||||
nameRu: 'Marmaris VIP Трансфер и Туры',
|
||||
descriptionTr: 'Dalaman Havalimanı transferleri ve Marmaris çevresindeki tarihi/doğal alanlara özel konforlu turlar. Güvenilir ve lüks taşımacılık.',
|
||||
descriptionEn: 'Dalaman Airport transfers and comfortable private tours to historical/natural areas around Marmaris. Reliable and luxurious transportation.',
|
||||
descriptionRu: 'Трансфер из аэропорта Даламан и комфортабельные частные туры по историческим и природным местам вокруг Мармариса. Надежный и роскошный транспорт.',
|
||||
address: 'Atatürk Caddesi No:102, Armutalan, Marmaris',
|
||||
phone: '+90 252 413 77 88',
|
||||
whatsapp: '+90 541 333 44 55',
|
||||
website: 'https://marmarisviptransfer.com',
|
||||
instagram: null,
|
||||
priceRange: 2,
|
||||
rating: 4.6,
|
||||
isLocalApproved: false,
|
||||
latitude: 36.8495,
|
||||
longitude: 28.2430,
|
||||
openingHours: { all: '24 Hours Open' },
|
||||
images: [
|
||||
{ id: 'img-8-1', listingId: 'list-8', url: 'https://images.unsplash.com/photo-1549317661-bd32c8ce0db2?w=800&auto=format&fit=crop&q=80' }
|
||||
],
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
}
|
||||
]
|
||||
|
||||
globalForMockDb.initialized = true
|
||||
}
|
||||
|
||||
export const mockDb = {
|
||||
// Config helpers
|
||||
isMock: () => process.env.USE_MOCK === 'true',
|
||||
|
||||
// Categories
|
||||
async getCategories() {
|
||||
if (this.isMock()) {
|
||||
return globalForMockDb.categories
|
||||
}
|
||||
return db.category.findMany({ orderBy: { nameTr: 'asc' } })
|
||||
},
|
||||
|
||||
async getCategoryBySlug(slug: string) {
|
||||
if (this.isMock()) {
|
||||
return globalForMockDb.categories.find(c => c.slug === slug) || null
|
||||
}
|
||||
return db.category.findUnique({ where: { slug } })
|
||||
},
|
||||
|
||||
async createCategory(data: { slug: string; nameTr: string; nameEn: string; nameRu: string }) {
|
||||
if (this.isMock()) {
|
||||
const newCat = { id: `cat-${Date.now()}`, ...data }
|
||||
globalForMockDb.categories.push(newCat)
|
||||
return newCat
|
||||
}
|
||||
return db.category.create({ data })
|
||||
},
|
||||
|
||||
async updateCategory(id: string, data: { slug: string; nameTr: string; nameEn: string; nameRu: string }) {
|
||||
if (this.isMock()) {
|
||||
const idx = globalForMockDb.categories.findIndex(c => c.id === id)
|
||||
if (idx !== -1) {
|
||||
globalForMockDb.categories[idx] = { ...globalForMockDb.categories[idx], ...data }
|
||||
return globalForMockDb.categories[idx]
|
||||
}
|
||||
return null
|
||||
}
|
||||
return db.category.update({ where: { id }, data })
|
||||
},
|
||||
|
||||
// Neighborhoods
|
||||
async getNeighborhoods() {
|
||||
if (this.isMock()) {
|
||||
return globalForMockDb.neighborhoods
|
||||
}
|
||||
return db.neighborhood.findMany({ orderBy: { nameTr: 'asc' } })
|
||||
},
|
||||
|
||||
async getNeighborhoodBySlug(slug: string) {
|
||||
if (this.isMock()) {
|
||||
return globalForMockDb.neighborhoods.find(n => n.slug === slug) || null
|
||||
}
|
||||
return db.neighborhood.findUnique({ where: { slug } })
|
||||
},
|
||||
|
||||
async createNeighborhood(data: { slug: string; nameTr: string; nameEn: string; nameRu: string }) {
|
||||
if (this.isMock()) {
|
||||
const newNeigh = { id: `neigh-${Date.now()}`, ...data }
|
||||
globalForMockDb.neighborhoods.push(newNeigh)
|
||||
return newNeigh
|
||||
}
|
||||
return db.neighborhood.create({ data })
|
||||
},
|
||||
|
||||
async updateNeighborhood(id: string, data: { slug: string; nameTr: string; nameEn: string; nameRu: string }) {
|
||||
if (this.isMock()) {
|
||||
const idx = globalForMockDb.neighborhoods.findIndex(n => n.id === id)
|
||||
if (idx !== -1) {
|
||||
globalForMockDb.neighborhoods[idx] = { ...globalForMockDb.neighborhoods[idx], ...data }
|
||||
return globalForMockDb.neighborhoods[idx]
|
||||
}
|
||||
return null
|
||||
}
|
||||
return db.neighborhood.update({ where: { id }, data })
|
||||
},
|
||||
|
||||
// Listings
|
||||
async getListings(filters?: {
|
||||
categoryId?: string
|
||||
neighborhoodId?: string
|
||||
priceRange?: number
|
||||
isLocalApproved?: boolean
|
||||
search?: string
|
||||
}) {
|
||||
if (this.isMock()) {
|
||||
let result = globalForMockDb.listings.filter(l => !l.deletedAt)
|
||||
if (filters) {
|
||||
if (filters.categoryId) result = result.filter(l => l.categoryId === filters.categoryId)
|
||||
if (filters.neighborhoodId) result = result.filter(l => l.neighborhoodId === filters.neighborhoodId)
|
||||
if (filters.priceRange) result = result.filter(l => l.priceRange === filters.priceRange)
|
||||
if (filters.isLocalApproved !== undefined) result = result.filter(l => l.isLocalApproved === filters.isLocalApproved)
|
||||
if (filters.search) {
|
||||
const s = filters.search.toLowerCase()
|
||||
result = result.filter(l =>
|
||||
l.nameTr.toLowerCase().includes(s) ||
|
||||
l.nameEn.toLowerCase().includes(s) ||
|
||||
l.nameRu.toLowerCase().includes(s) ||
|
||||
l.address.toLowerCase().includes(s)
|
||||
)
|
||||
}
|
||||
}
|
||||
return result.map(l => ({
|
||||
...l,
|
||||
category: globalForMockDb.categories.find(c => c.id === l.categoryId),
|
||||
neighborhood: globalForMockDb.neighborhoods.find(n => n.id === l.neighborhoodId)
|
||||
}))
|
||||
}
|
||||
|
||||
const where: any = { deletedAt: null }
|
||||
if (filters) {
|
||||
if (filters.categoryId) where.categoryId = filters.categoryId
|
||||
if (filters.neighborhoodId) where.neighborhoodId = filters.neighborhoodId
|
||||
if (filters.priceRange) where.priceRange = filters.priceRange
|
||||
if (filters.isLocalApproved !== undefined) where.isLocalApproved = filters.isLocalApproved
|
||||
if (filters.search) {
|
||||
where.OR = [
|
||||
{ nameTr: { contains: filters.search, mode: 'insensitive' } },
|
||||
{ nameEn: { contains: filters.search, mode: 'insensitive' } },
|
||||
{ nameRu: { contains: filters.search, mode: 'insensitive' } },
|
||||
{ address: { contains: filters.search, mode: 'insensitive' } },
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
return db.listing.findMany({
|
||||
where,
|
||||
include: { category: true, neighborhood: true, images: true },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
},
|
||||
|
||||
async getListingBySlug(slug: string) {
|
||||
if (this.isMock()) {
|
||||
const listing = globalForMockDb.listings.find(l => l.slug === slug && !l.deletedAt)
|
||||
if (!listing) return null
|
||||
return {
|
||||
...listing,
|
||||
category: globalForMockDb.categories.find(c => c.id === listing.categoryId),
|
||||
neighborhood: globalForMockDb.neighborhoods.find(n => n.id === listing.neighborhoodId)
|
||||
}
|
||||
}
|
||||
return db.listing.findUnique({
|
||||
where: { slug },
|
||||
include: { category: true, neighborhood: true, images: true }
|
||||
})
|
||||
},
|
||||
|
||||
async getListingById(id: string) {
|
||||
if (this.isMock()) {
|
||||
const listing = globalForMockDb.listings.find(l => l.id === id && !l.deletedAt)
|
||||
if (!listing) return null
|
||||
return {
|
||||
...listing,
|
||||
category: globalForMockDb.categories.find(c => c.id === listing.categoryId),
|
||||
neighborhood: globalForMockDb.neighborhoods.find(n => n.id === listing.neighborhoodId)
|
||||
}
|
||||
}
|
||||
return db.listing.findUnique({
|
||||
where: { id },
|
||||
include: { category: true, neighborhood: true, images: true }
|
||||
})
|
||||
},
|
||||
|
||||
async createListing(data: Omit<Listing, 'id' | 'images' | 'createdAt' | 'updatedAt'> & { images?: string[] }) {
|
||||
const { images, category, neighborhood, ...rest } = data
|
||||
if (this.isMock()) {
|
||||
const newId = `list-${Date.now()}`
|
||||
const gallery = (images || []).map((url, i) => ({ id: `img-${newId}-${i}`, listingId: newId, url }))
|
||||
const newListing: Listing = {
|
||||
id: newId,
|
||||
...rest,
|
||||
images: gallery,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
}
|
||||
globalForMockDb.listings.unshift(newListing)
|
||||
return newListing
|
||||
}
|
||||
|
||||
return db.listing.create({
|
||||
data: {
|
||||
...rest,
|
||||
images: images ? { create: images.map(url => ({ url })) } : undefined
|
||||
},
|
||||
include: { images: true }
|
||||
})
|
||||
},
|
||||
|
||||
async updateListing(id: string, data: Partial<Omit<Listing, 'id' | 'images' | 'createdAt' | 'updatedAt'>> & { images?: string[] }) {
|
||||
const { images, category, neighborhood, ...rest } = data
|
||||
if (this.isMock()) {
|
||||
const idx = globalForMockDb.listings.findIndex(l => l.id === id)
|
||||
if (idx !== -1) {
|
||||
const oldListing = globalForMockDb.listings[idx]
|
||||
const updatedGallery = images
|
||||
? images.map((url, i) => ({ id: `img-${id}-${i}`, listingId: id, url }))
|
||||
: oldListing.images
|
||||
const updated = {
|
||||
...oldListing,
|
||||
...rest,
|
||||
images: updatedGallery,
|
||||
updatedAt: new Date()
|
||||
} as Listing
|
||||
globalForMockDb.listings[idx] = updated
|
||||
return updated
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
if (images) {
|
||||
await db.gallery.deleteMany({ where: { listingId: id } })
|
||||
}
|
||||
|
||||
return db.listing.update({
|
||||
where: { id },
|
||||
data: {
|
||||
...rest,
|
||||
images: images ? { create: images.map(url => ({ url })) } : undefined
|
||||
},
|
||||
include: { images: true }
|
||||
})
|
||||
},
|
||||
|
||||
async deleteListing(id: string) {
|
||||
if (this.isMock()) {
|
||||
const idx = globalForMockDb.listings.findIndex(l => l.id === id)
|
||||
if (idx !== -1) {
|
||||
globalForMockDb.listings[idx].deletedAt = new Date()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
await db.listing.update({
|
||||
where: { id },
|
||||
data: { deletedAt: new Date() }
|
||||
})
|
||||
return true
|
||||
},
|
||||
|
||||
// Business Submissions
|
||||
async getSubmissions() {
|
||||
if (this.isMock()) {
|
||||
return globalForMockDb.submissions.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
|
||||
}
|
||||
return db.businessSubmission.findMany({ orderBy: { createdAt: 'desc' } })
|
||||
},
|
||||
|
||||
async createSubmission(data: Omit<BusinessSubmission, 'id' | 'status' | 'createdAt' | 'updatedAt'>) {
|
||||
if (this.isMock()) {
|
||||
const newSub: BusinessSubmission = {
|
||||
id: `sub-${Date.now()}`,
|
||||
...data,
|
||||
status: 'PENDING',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
}
|
||||
globalForMockDb.submissions.push(newSub)
|
||||
return newSub
|
||||
}
|
||||
return db.businessSubmission.create({ data: { ...data, status: 'PENDING' } })
|
||||
},
|
||||
|
||||
async updateSubmissionStatus(id: string, status: 'APPROVED' | 'REJECTED') {
|
||||
if (this.isMock()) {
|
||||
const idx = globalForMockDb.submissions.findIndex(s => s.id === id)
|
||||
if (idx !== -1) {
|
||||
globalForMockDb.submissions[idx].status = status
|
||||
globalForMockDb.submissions[idx].updatedAt = new Date()
|
||||
return globalForMockDb.submissions[idx]
|
||||
}
|
||||
return null
|
||||
}
|
||||
return db.businessSubmission.update({
|
||||
where: { id },
|
||||
data: { status }
|
||||
})
|
||||
},
|
||||
|
||||
async getSubmissionById(id: string) {
|
||||
if (this.isMock()) {
|
||||
return globalForMockDb.submissions.find(s => s.id === id) || null
|
||||
}
|
||||
return db.businessSubmission.findUnique({ where: { id } })
|
||||
},
|
||||
|
||||
// Contact Messages
|
||||
async getMessages() {
|
||||
if (this.isMock()) {
|
||||
return globalForMockDb.messages.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
|
||||
}
|
||||
return db.contactMessage.findMany({ orderBy: { createdAt: 'desc' } })
|
||||
},
|
||||
|
||||
async createMessage(data: Omit<ContactMessage, 'id' | 'isRead' | 'createdAt' | 'updatedAt'>) {
|
||||
if (this.isMock()) {
|
||||
const newMsg: ContactMessage = {
|
||||
id: `msg-${Date.now()}`,
|
||||
...data,
|
||||
isRead: false,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
}
|
||||
globalForMockDb.messages.push(newMsg)
|
||||
return newMsg
|
||||
}
|
||||
return db.contactMessage.create({ data: { ...data, isRead: false } })
|
||||
},
|
||||
|
||||
async markMessageAsRead(id: string) {
|
||||
if (this.isMock()) {
|
||||
const idx = globalForMockDb.messages.findIndex(m => m.id === id)
|
||||
if (idx !== -1) {
|
||||
globalForMockDb.messages[idx].isRead = true
|
||||
globalForMockDb.messages[idx].updatedAt = new Date()
|
||||
return globalForMockDb.messages[idx]
|
||||
}
|
||||
return null
|
||||
}
|
||||
return db.contactMessage.update({
|
||||
where: { id },
|
||||
data: { isRead: true }
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user