feat: implement dynamic categories, admin category CRUD, fix routing and cleanup
This commit is contained in:
+466
-12
@@ -56,6 +56,11 @@ export interface Listing {
|
||||
menuUrl?: string | null
|
||||
isFeatured: boolean
|
||||
featuredUntil?: Date | null
|
||||
|
||||
// Phase 3
|
||||
hasWidgetInstalled: boolean
|
||||
widgetInstalledAt?: Date | null
|
||||
widgetSiteUrl?: string | null
|
||||
}
|
||||
|
||||
export interface BusinessSubmission {
|
||||
@@ -133,6 +138,46 @@ export interface InstagramFeedCache {
|
||||
fetchedAt: Date
|
||||
}
|
||||
|
||||
export interface ListingAnalyticsDaily {
|
||||
id: string
|
||||
listingId: string
|
||||
date: Date
|
||||
views: number
|
||||
whatsappClicks: number
|
||||
phoneClicks: number
|
||||
menuClicks: number
|
||||
}
|
||||
|
||||
export interface Event {
|
||||
id: string
|
||||
slug: string
|
||||
listingId?: string | null
|
||||
titleTr: string
|
||||
titleEn: string
|
||||
titleRu: string
|
||||
descriptionTr: string
|
||||
descriptionEn: string
|
||||
descriptionRu: string
|
||||
startDate: Date
|
||||
endDate?: Date | null
|
||||
coverImage?: string | null
|
||||
isSponsored: boolean
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
deletedAt?: Date | null
|
||||
}
|
||||
|
||||
export interface GeneratedItinerary {
|
||||
id: string
|
||||
paramsHash: string
|
||||
params: any
|
||||
content: string
|
||||
listingIds: string[]
|
||||
createdAt: Date
|
||||
}
|
||||
|
||||
const MOCK_DB_VERSION = 4
|
||||
|
||||
const globalForMockDb = globalThis as unknown as {
|
||||
categories: Category[]
|
||||
neighborhoods: Neighborhood[]
|
||||
@@ -142,10 +187,14 @@ const globalForMockDb = globalThis as unknown as {
|
||||
blogPosts: BlogPost[]
|
||||
collections: Collection[]
|
||||
instagramFeedCaches: InstagramFeedCache[]
|
||||
listingAnalyticsDaily: ListingAnalyticsDaily[]
|
||||
events: Event[]
|
||||
generatedItineraries: GeneratedItinerary[]
|
||||
initialized: boolean
|
||||
__version: number
|
||||
}
|
||||
|
||||
if (!globalForMockDb.initialized) {
|
||||
if (!globalForMockDb.initialized || globalForMockDb.__version !== MOCK_DB_VERSION) {
|
||||
globalForMockDb.categories = [
|
||||
{ id: 'cat-1', slug: 'restoran', nameTr: 'Restoran', nameEn: 'Restaurant', nameRu: 'Ресторан' },
|
||||
{ id: 'cat-2', slug: 'apart', nameTr: 'Apart', nameEn: 'Apart Hotel', nameRu: 'Апарт-отель' },
|
||||
@@ -163,7 +212,7 @@ if (!globalForMockDb.initialized) {
|
||||
globalForMockDb.submissions = []
|
||||
globalForMockDb.messages = []
|
||||
|
||||
// Seed Listings with Phase 2 fields
|
||||
// Seed Listings with Phase 2 & 3 fields
|
||||
globalForMockDb.listings = [
|
||||
{
|
||||
id: 'list-1',
|
||||
@@ -173,7 +222,7 @@ if (!globalForMockDb.initialized) {
|
||||
city: 'marmaris',
|
||||
nameTr: 'İskele Balık Ocakbaşı',
|
||||
nameEn: 'Iskele Fish & Grill',
|
||||
nameRu: 'Рыбный Гриль Искеле',
|
||||
nameRu: 'Рыбный Гриль İskele',
|
||||
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: 'Ваша местная гастрономическая остановка в Марине со свежей эгейской рыбой и традиционными закусками. Ужин в сопровождении великолепного вида на залив.',
|
||||
@@ -195,7 +244,10 @@ if (!globalForMockDb.initialized) {
|
||||
createdAt: new Date(Date.now() - 3600000 * 24 * 5),
|
||||
updatedAt: new Date(),
|
||||
menuUrl: 'https://iskelemarmaris.com/menu',
|
||||
isFeatured: true
|
||||
isFeatured: true,
|
||||
hasWidgetInstalled: true,
|
||||
widgetInstalledAt: new Date(Date.now() - 3600000 * 24 * 10),
|
||||
widgetSiteUrl: 'https://iskelemarmaris.com'
|
||||
},
|
||||
{
|
||||
id: 'list-2',
|
||||
@@ -226,7 +278,10 @@ if (!globalForMockDb.initialized) {
|
||||
createdAt: new Date(Date.now() - 3600000 * 24 * 4),
|
||||
updatedAt: new Date(),
|
||||
menuUrl: 'https://mavibeyazmarmaris.com/digital-menu',
|
||||
isFeatured: false
|
||||
isFeatured: false,
|
||||
hasWidgetInstalled: false,
|
||||
widgetInstalledAt: null,
|
||||
widgetSiteUrl: null
|
||||
},
|
||||
{
|
||||
id: 'list-3',
|
||||
@@ -256,7 +311,10 @@ if (!globalForMockDb.initialized) {
|
||||
],
|
||||
createdAt: new Date(Date.now() - 3600000 * 24 * 3),
|
||||
updatedAt: new Date(),
|
||||
isFeatured: false
|
||||
isFeatured: false,
|
||||
hasWidgetInstalled: false,
|
||||
widgetInstalledAt: null,
|
||||
widgetSiteUrl: null
|
||||
},
|
||||
{
|
||||
id: 'list-4',
|
||||
@@ -287,7 +345,10 @@ if (!globalForMockDb.initialized) {
|
||||
],
|
||||
createdAt: new Date(Date.now() - 3600000 * 24 * 2),
|
||||
updatedAt: new Date(),
|
||||
isFeatured: true
|
||||
isFeatured: true,
|
||||
hasWidgetInstalled: false,
|
||||
widgetInstalledAt: null,
|
||||
widgetSiteUrl: null
|
||||
},
|
||||
{
|
||||
id: 'list-5',
|
||||
@@ -317,7 +378,10 @@ if (!globalForMockDb.initialized) {
|
||||
],
|
||||
createdAt: new Date(Date.now() - 3600000 * 24 * 1),
|
||||
updatedAt: new Date(),
|
||||
isFeatured: false
|
||||
isFeatured: false,
|
||||
hasWidgetInstalled: false,
|
||||
widgetInstalledAt: null,
|
||||
widgetSiteUrl: null
|
||||
},
|
||||
{
|
||||
id: 'list-6',
|
||||
@@ -330,7 +394,7 @@ if (!globalForMockDb.initialized) {
|
||||
nameRu: 'Дайвинг-центр Марина',
|
||||
descriptionTr: 'Marmaris\'in kristal netliğindeki sularında profesyonel eğitmenlerle dalış eğitimleri ve günlük dalış turları. CMAS ve PADI sertifikalı eğitimler.',
|
||||
descriptionEn: 'Diving training and daily diving tours in the crystal clear waters of Marmaris with professional instructors. CMAS and PADI certified courses.',
|
||||
descriptionRu: 'Обучение дайвингу и ежедневные дайв-туры в кристально чистых водах Мармариса с профессиональными инструкторами. Курсы с сертификатом CMAS и PADI.',
|
||||
descriptionRu: 'Обучение дайвингу и ежедневные дайв-туры в кристально чистых водах Мармариса с профессиональными инструкторами. Курсы с сертификатом CMAS и ПАДИ.',
|
||||
address: 'Yat Limanı Belediye İskelesi, Marmaris',
|
||||
phone: '+90 532 234 56 78',
|
||||
whatsapp: '+90 532 234 56 78',
|
||||
@@ -347,7 +411,10 @@ if (!globalForMockDb.initialized) {
|
||||
],
|
||||
createdAt: new Date(Date.now() - 3600000 * 12),
|
||||
updatedAt: new Date(),
|
||||
isFeatured: false
|
||||
isFeatured: false,
|
||||
hasWidgetInstalled: false,
|
||||
widgetInstalledAt: null,
|
||||
widgetSiteUrl: null
|
||||
},
|
||||
{
|
||||
id: 'list-7',
|
||||
@@ -377,7 +444,10 @@ if (!globalForMockDb.initialized) {
|
||||
],
|
||||
createdAt: new Date(Date.now() - 3600000 * 6),
|
||||
updatedAt: new Date(),
|
||||
isFeatured: false
|
||||
isFeatured: false,
|
||||
hasWidgetInstalled: false,
|
||||
widgetInstalledAt: null,
|
||||
widgetSiteUrl: null
|
||||
},
|
||||
{
|
||||
id: 'list-8',
|
||||
@@ -407,7 +477,10 @@ if (!globalForMockDb.initialized) {
|
||||
],
|
||||
createdAt: new Date(Date.now() - 3600000 * 2),
|
||||
updatedAt: new Date(),
|
||||
isFeatured: false
|
||||
isFeatured: false,
|
||||
hasWidgetInstalled: false,
|
||||
widgetInstalledAt: null,
|
||||
widgetSiteUrl: null
|
||||
}
|
||||
]
|
||||
|
||||
@@ -504,6 +577,52 @@ if (!globalForMockDb.initialized) {
|
||||
}
|
||||
]
|
||||
|
||||
// Seed Listing Analytics Daily
|
||||
globalForMockDb.listingAnalyticsDaily = [
|
||||
{ id: 'an-1', listingId: 'list-1', date: new Date(), views: 120, whatsappClicks: 14, phoneClicks: 5, menuClicks: 22 },
|
||||
{ id: 'an-2', listingId: 'list-2', date: new Date(), views: 45, whatsappClicks: 2, phoneClicks: 1, menuClicks: 0 }
|
||||
]
|
||||
|
||||
// Seed Events
|
||||
globalForMockDb.events = [
|
||||
{
|
||||
id: 'evt-1',
|
||||
slug: 'iskele-caz-gecesi',
|
||||
listingId: 'list-1',
|
||||
titleTr: 'İskele Caz Gecesi',
|
||||
titleEn: 'Iskele Jazz Night',
|
||||
titleRu: 'Джазовый вечер Искеле',
|
||||
descriptionTr: 'Marmaris Marina\'da deniz esintisi eşliğinde canlı caz müziği ve özel akşam yemeği menüsü.',
|
||||
descriptionEn: 'Live jazz music and special dinner menu accompanied by sea breeze at Marmaris Marina.',
|
||||
descriptionRu: 'Живая джазовая музыка и специальное меню ужина в сопровождении морского бриза в Мармарис Марине.',
|
||||
startDate: new Date(Date.now() + 3600000 * 24 * 3), // 3 days from now
|
||||
endDate: new Date(Date.now() + 3600000 * 24 * 3 + 3600000 * 4),
|
||||
coverImage: 'https://images.unsplash.com/photo-1511192336575-5a79af67a629?w=800&auto=format&fit=crop&q=80',
|
||||
isSponsored: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
id: 'evt-2',
|
||||
slug: 'siteler-havuz-partisi',
|
||||
listingId: 'list-2',
|
||||
titleTr: 'Yaz Ortası Havuz Partisi',
|
||||
titleEn: 'Midsummer Pool Party',
|
||||
titleRu: 'Летняя вечеринка у бассейна',
|
||||
descriptionTr: 'Sınırsız müzik, dj performansları ve eğlenceli havuz aktiviteleri.',
|
||||
descriptionEn: 'Unlimited music, DJ performances and fun pool activities.',
|
||||
descriptionRu: 'Безлимитная музыка, диджейские сеты и веселые развлечения у бассейна.',
|
||||
startDate: new Date(Date.now() + 3600000 * 24 * 7), // 7 days from now
|
||||
coverImage: 'https://images.unsplash.com/photo-1576013551627-0cc20b96c2a7?w=800&auto=format&fit=crop&q=80',
|
||||
isSponsored: false,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
}
|
||||
]
|
||||
|
||||
globalForMockDb.generatedItineraries = []
|
||||
|
||||
globalForMockDb.__version = MOCK_DB_VERSION
|
||||
globalForMockDb.initialized = true
|
||||
}
|
||||
|
||||
@@ -512,6 +631,41 @@ export const mockDb = {
|
||||
isMock: () => process.env.USE_MOCK === 'true',
|
||||
|
||||
// Categories
|
||||
async createCategory(data: Omit<Category, 'id' | 'createdAt' | 'updatedAt'>) {
|
||||
if (this.isMock()) {
|
||||
const newCat: Category = {
|
||||
id: 'cat-' + Date.now(),
|
||||
...data,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
}
|
||||
globalForMockDb.categories.push(newCat)
|
||||
return newCat
|
||||
}
|
||||
return db.category.create({ data })
|
||||
},
|
||||
|
||||
async updateCategory(id: string, data: Partial<Omit<Category, 'id' | 'createdAt' | 'updatedAt'>>) {
|
||||
if (this.isMock()) {
|
||||
const idx = globalForMockDb.categories.findIndex(c => c.id === id)
|
||||
if (idx > -1) {
|
||||
globalForMockDb.categories[idx] = { ...globalForMockDb.categories[idx], ...data, updatedAt: new Date() }
|
||||
return globalForMockDb.categories[idx]
|
||||
}
|
||||
return null
|
||||
}
|
||||
return db.category.update({ where: { id }, data })
|
||||
},
|
||||
|
||||
async deleteCategory(id: string) {
|
||||
if (this.isMock()) {
|
||||
globalForMockDb.categories = globalForMockDb.categories.filter(c => c.id !== id)
|
||||
return true
|
||||
}
|
||||
await db.category.delete({ where: { id } })
|
||||
return true
|
||||
},
|
||||
|
||||
async getCategories() {
|
||||
if (this.isMock()) {
|
||||
return globalForMockDb.categories
|
||||
@@ -555,6 +709,38 @@ export const mockDb = {
|
||||
return db.neighborhood.findUnique({ where: { id } })
|
||||
},
|
||||
|
||||
async createNeighborhood(data: Omit<Neighborhood, 'id' | 'createdAt' | 'updatedAt'>) {
|
||||
if (this.isMock()) {
|
||||
const newNeigh: Neighborhood = {
|
||||
id: `neigh-${Date.now()}`,
|
||||
...data,
|
||||
}
|
||||
globalForMockDb.neighborhoods.push(newNeigh)
|
||||
return newNeigh
|
||||
}
|
||||
return db.neighborhood.create({ data })
|
||||
},
|
||||
|
||||
async updateNeighborhood(id: string, data: Partial<Omit<Neighborhood, 'id' | 'createdAt' | 'updatedAt'>>) {
|
||||
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]
|
||||
}
|
||||
throw new Error('Mahalle bulunamadı')
|
||||
}
|
||||
return db.neighborhood.update({ where: { id }, data })
|
||||
},
|
||||
|
||||
async deleteNeighborhood(id: string) {
|
||||
if (this.isMock()) {
|
||||
globalForMockDb.neighborhoods = globalForMockDb.neighborhoods.filter(n => n.id !== id)
|
||||
return
|
||||
}
|
||||
return db.neighborhood.delete({ where: { id } })
|
||||
},
|
||||
|
||||
// Listings CRUD
|
||||
async getListings(filters?: {
|
||||
categoryId?: string
|
||||
@@ -736,6 +922,50 @@ export const mockDb = {
|
||||
return true
|
||||
},
|
||||
|
||||
async getDeletedListings() {
|
||||
if (this.isMock()) {
|
||||
const result = globalForMockDb.listings.filter(l => l.deletedAt)
|
||||
return result.map(l => ({
|
||||
...l,
|
||||
category: globalForMockDb.categories.find(c => c.id === l.categoryId),
|
||||
neighborhood: globalForMockDb.neighborhoods.find(n => n.id === l.neighborhoodId)
|
||||
}))
|
||||
}
|
||||
return db.listing.findMany({
|
||||
where: { deletedAt: { not: null } },
|
||||
include: {
|
||||
category: true,
|
||||
neighborhood: true
|
||||
},
|
||||
orderBy: { deletedAt: 'desc' }
|
||||
})
|
||||
},
|
||||
|
||||
async restoreListing(id: string) {
|
||||
if (this.isMock()) {
|
||||
const idx = globalForMockDb.listings.findIndex(l => l.id === id)
|
||||
if (idx !== -1) {
|
||||
globalForMockDb.listings[idx].deletedAt = null
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
await db.listing.update({
|
||||
where: { id },
|
||||
data: { deletedAt: null }
|
||||
})
|
||||
return true
|
||||
},
|
||||
|
||||
async hardDeleteListing(id: string) {
|
||||
if (this.isMock()) {
|
||||
globalForMockDb.listings = globalForMockDb.listings.filter(l => l.id !== id)
|
||||
return true
|
||||
}
|
||||
await db.listing.delete({ where: { id } })
|
||||
return true
|
||||
},
|
||||
|
||||
// Business Submissions
|
||||
async getSubmissions() {
|
||||
if (this.isMock()) {
|
||||
@@ -1046,5 +1276,229 @@ export const mockDb = {
|
||||
update: { handle, posts, fetchedAt: new Date() },
|
||||
create: { listingId, handle, posts, fetchedAt: new Date() }
|
||||
})
|
||||
},
|
||||
|
||||
// Phase 3 Widget & Backlink
|
||||
async getWidgetPartners() {
|
||||
if (this.isMock()) {
|
||||
return globalForMockDb.listings.filter(l => l.hasWidgetInstalled && !l.deletedAt)
|
||||
}
|
||||
return db.listing.findMany({
|
||||
where: { hasWidgetInstalled: true, deletedAt: null },
|
||||
include: { category: true, neighborhood: true }
|
||||
})
|
||||
},
|
||||
|
||||
// Phase 3 Analytics daily aggregates
|
||||
async getAnalytics(listingId: string, startDate?: Date, endDate?: Date) {
|
||||
if (this.isMock()) {
|
||||
let data = globalForMockDb.listingAnalyticsDaily.filter(a => a.listingId === listingId)
|
||||
if (startDate) {
|
||||
data = data.filter(a => a.date >= startDate)
|
||||
}
|
||||
if (endDate) {
|
||||
data = data.filter(a => a.date <= endDate)
|
||||
}
|
||||
return data
|
||||
}
|
||||
return db.listingAnalyticsDaily.findMany({
|
||||
where: {
|
||||
listingId,
|
||||
date: {
|
||||
gte: startDate,
|
||||
lte: endDate
|
||||
}
|
||||
},
|
||||
orderBy: { date: 'asc' }
|
||||
})
|
||||
},
|
||||
|
||||
async incrementAnalytics(listingId: string, actionType: 'views' | 'whatsapp' | 'phone' | 'menu') {
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
|
||||
if (this.isMock()) {
|
||||
const record = globalForMockDb.listingAnalyticsDaily.find(a =>
|
||||
a.listingId === listingId &&
|
||||
a.date.getTime() === today.getTime()
|
||||
)
|
||||
|
||||
const keyMap: Record<string, keyof ListingAnalyticsDaily> = {
|
||||
views: 'views',
|
||||
whatsapp: 'whatsappClicks',
|
||||
phone: 'phoneClicks',
|
||||
menu: 'menuClicks'
|
||||
}
|
||||
const mappedKey = keyMap[actionType]
|
||||
|
||||
if (record) {
|
||||
(record[mappedKey] as number) += 1
|
||||
return record
|
||||
} else {
|
||||
const newRecord: ListingAnalyticsDaily = {
|
||||
id: `an-${Date.now()}`,
|
||||
listingId,
|
||||
date: today,
|
||||
views: actionType === 'views' ? 1 : 0,
|
||||
whatsappClicks: actionType === 'whatsapp' ? 1 : 0,
|
||||
phoneClicks: actionType === 'phone' ? 1 : 0,
|
||||
menuClicks: actionType === 'menu' ? 1 : 0
|
||||
}
|
||||
globalForMockDb.listingAnalyticsDaily.push(newRecord)
|
||||
return newRecord
|
||||
}
|
||||
}
|
||||
|
||||
const keyMap = {
|
||||
views: 'views',
|
||||
whatsapp: 'whatsappClicks',
|
||||
phone: 'phoneClicks',
|
||||
menu: 'menuClicks'
|
||||
}
|
||||
const updateKey = keyMap[actionType] as 'views' | 'whatsappClicks' | 'phoneClicks' | 'menuClicks'
|
||||
|
||||
return db.listingAnalyticsDaily.upsert({
|
||||
where: {
|
||||
listingId_date: {
|
||||
listingId,
|
||||
date: today
|
||||
}
|
||||
},
|
||||
update: {
|
||||
[updateKey]: { increment: 1 }
|
||||
},
|
||||
create: {
|
||||
listingId,
|
||||
date: today,
|
||||
views: actionType === 'views' ? 1 : 0,
|
||||
whatsappClicks: actionType === 'whatsapp' ? 1 : 0,
|
||||
phoneClicks: actionType === 'phone' ? 1 : 0,
|
||||
menuClicks: actionType === 'menu' ? 1 : 0
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// Phase 3 Events
|
||||
async getEvents(onlyActive = false) {
|
||||
if (this.isMock()) {
|
||||
let evts = globalForMockDb.events.filter(e => !e.deletedAt)
|
||||
if (onlyActive) {
|
||||
evts = evts.filter(e => e.startDate >= new Date())
|
||||
}
|
||||
evts.sort((a, b) => a.startDate.getTime() - b.startDate.getTime())
|
||||
return evts.map(e => ({
|
||||
...e,
|
||||
listing: globalForMockDb.listings.find(l => l.id === e.listingId)
|
||||
}))
|
||||
}
|
||||
return db.event.findMany({
|
||||
where: {
|
||||
deletedAt: null,
|
||||
...(onlyActive ? { startDate: { gte: new Date() } } : {})
|
||||
},
|
||||
include: { listing: { include: { category: true, neighborhood: true } } },
|
||||
orderBy: { startDate: 'asc' }
|
||||
})
|
||||
},
|
||||
|
||||
async getEventBySlug(slug: string) {
|
||||
if (this.isMock()) {
|
||||
const e = globalForMockDb.events.find(evt => evt.slug === slug && !evt.deletedAt)
|
||||
if (!e) return null
|
||||
return {
|
||||
...e,
|
||||
listing: globalForMockDb.listings.find(l => l.id === e.listingId)
|
||||
}
|
||||
}
|
||||
return db.event.findUnique({
|
||||
where: { slug },
|
||||
include: { listing: { include: { category: true, neighborhood: true } } }
|
||||
})
|
||||
},
|
||||
|
||||
async getEventById(id: string) {
|
||||
if (this.isMock()) {
|
||||
const e = globalForMockDb.events.find(evt => evt.id === id && !evt.deletedAt)
|
||||
if (!e) return null
|
||||
return {
|
||||
...e,
|
||||
listing: globalForMockDb.listings.find(l => l.id === e.listingId)
|
||||
}
|
||||
}
|
||||
return db.event.findUnique({
|
||||
where: { id },
|
||||
include: { listing: { include: { category: true, neighborhood: true } } }
|
||||
})
|
||||
},
|
||||
|
||||
async createEvent(data: Omit<Event, 'id' | 'createdAt' | 'updatedAt' | 'deletedAt'>) {
|
||||
if (this.isMock()) {
|
||||
const newEvt: Event = {
|
||||
id: `evt-${Date.now()}`,
|
||||
...data,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
}
|
||||
globalForMockDb.events.push(newEvt)
|
||||
return newEvt
|
||||
}
|
||||
return db.event.create({ data })
|
||||
},
|
||||
|
||||
async updateEvent(id: string, data: Partial<Omit<Event, 'id' | 'createdAt' | 'updatedAt' | 'deletedAt'>>) {
|
||||
if (this.isMock()) {
|
||||
const idx = globalForMockDb.events.findIndex(e => e.id === id)
|
||||
if (idx !== -1) {
|
||||
globalForMockDb.events[idx] = {
|
||||
...globalForMockDb.events[idx],
|
||||
...data,
|
||||
updatedAt: new Date()
|
||||
} as Event
|
||||
return globalForMockDb.events[idx]
|
||||
}
|
||||
return null
|
||||
}
|
||||
return db.event.update({ where: { id }, data })
|
||||
},
|
||||
|
||||
async deleteEvent(id: string) {
|
||||
if (this.isMock()) {
|
||||
const idx = globalForMockDb.events.findIndex(e => e.id === id)
|
||||
if (idx !== -1) {
|
||||
globalForMockDb.events.splice(idx, 1)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
await db.event.delete({ where: { id } })
|
||||
return true
|
||||
},
|
||||
|
||||
// Phase 3 AI Itineraries
|
||||
async getItineraryByHash(hash: string) {
|
||||
if (this.isMock()) {
|
||||
return globalForMockDb.generatedItineraries.find(i => i.paramsHash === hash) || null
|
||||
}
|
||||
return db.generatedItinerary.findUnique({ where: { paramsHash: hash } })
|
||||
},
|
||||
|
||||
async getItineraryById(id: string) {
|
||||
if (this.isMock()) {
|
||||
return globalForMockDb.generatedItineraries.find(i => i.id === id) || null
|
||||
}
|
||||
return db.generatedItinerary.findUnique({ where: { id } })
|
||||
},
|
||||
|
||||
async createItinerary(data: Omit<GeneratedItinerary, 'id' | 'createdAt'>) {
|
||||
if (this.isMock()) {
|
||||
const newItin: GeneratedItinerary = {
|
||||
id: `itin-${Date.now()}`,
|
||||
...data,
|
||||
createdAt: new Date()
|
||||
}
|
||||
globalForMockDb.generatedItineraries.push(newItin)
|
||||
return newItin
|
||||
}
|
||||
return db.generatedItinerary.create({ data })
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user