feat: implement dynamic categories, admin category CRUD, fix routing and cleanup

This commit is contained in:
AyrisAI
2026-07-13 13:51:48 +03:00
parent 2f2dafcfb9
commit 5b44c78396
54 changed files with 3619 additions and 473 deletions
+53
View File
@@ -126,6 +126,13 @@ model Listing {
featuredUntil DateTime?
collections Collection[] @relation("CollectionListings")
instagramFeed InstagramFeedCache?
// Phase 3 Fields
hasWidgetInstalled Boolean @default(false)
widgetInstalledAt DateTime?
widgetSiteUrl String?
analytics ListingAnalyticsDaily[]
events Event[]
}
model Gallery {
@@ -210,3 +217,49 @@ model InstagramFeedCache {
listing Listing @relation(fields: [listingId], references: [id], onDelete: Cascade)
}
model ListingAnalyticsDaily {
id String @id @default(cuid())
listingId String
date DateTime
views Int @default(0)
whatsappClicks Int @default(0)
phoneClicks Int @default(0)
menuClicks Int @default(0)
listing Listing @relation(fields: [listingId], references: [id], onDelete: Cascade)
@@unique([listingId, date])
}
model Event {
id String @id @default(cuid())
slug String @unique
listingId String?
titleTr String
titleEn String
titleRu String
descriptionTr String @db.Text
descriptionEn String @db.Text
descriptionRu String @db.Text
startDate DateTime
endDate DateTime?
coverImage String? // Openinary
isSponsored Boolean @default(false)
listing Listing? @relation(fields: [listingId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
}
model GeneratedItinerary {
id String @id @default(cuid())
paramsHash String @unique
params Json // budget, groupType, interests, days
content String @db.Text
listingIds String[]
createdAt DateTime @default(now())
}
+154
View File
@@ -0,0 +1,154 @@
import { PrismaClient } from '@prisma/client'
import { mockDb } from '../lib/mockDb'
const prisma = new PrismaClient()
async function main() {
console.log('🌱 Bütün mock verileri PostgreSQL veritabanına aktarılıyor...')
// Force mockDb to use in-memory data for seeding
process.env.USE_MOCK = 'true'
// 1. Kategoriler
console.log('Kategoriler ekleniyor...')
const categories = await mockDb.getCategories()
for (const cat of categories) {
await prisma.category.upsert({
where: { id: cat.id },
update: {},
create: {
id: cat.id,
slug: cat.slug,
nameTr: cat.nameTr,
nameEn: cat.nameEn,
nameRu: cat.nameRu,
},
})
}
// 2. Mahalleler
console.log('Mahalleler ekleniyor...')
const neighborhoods = await mockDb.getNeighborhoods()
for (const neigh of neighborhoods) {
await prisma.neighborhood.upsert({
where: { id: neigh.id },
update: {},
create: {
id: neigh.id,
slug: neigh.slug,
nameTr: neigh.nameTr,
nameEn: neigh.nameEn,
nameRu: neigh.nameRu,
},
})
}
// 3. Mekanlar (Listings)
console.log('Mekanlar ekleniyor...')
const listings = await mockDb.getListings() // Default is 100 limit, mockDb returns all for seed if not paginated
// Let's ensure we get all
const allListings = await mockDb.getListings({ limit: 1000 })
for (const listing of allListings) {
await prisma.listing.upsert({
where: { id: listing.id },
update: {},
create: {
id: listing.id,
slug: listing.slug,
categoryId: listing.categoryId,
neighborhoodId: listing.neighborhoodId,
city: listing.city,
nameTr: listing.nameTr,
nameEn: listing.nameEn,
nameRu: listing.nameRu,
descriptionTr: listing.descriptionTr,
descriptionEn: listing.descriptionEn,
descriptionRu: listing.descriptionRu,
address: listing.address,
phone: listing.phone,
whatsapp: listing.whatsapp,
website: listing.website,
instagram: listing.instagram,
priceRange: listing.priceRange,
rating: listing.rating,
isLocalApproved: listing.isLocalApproved,
latitude: listing.latitude,
longitude: listing.longitude,
openingHours: listing.openingHours ? JSON.parse(JSON.stringify(listing.openingHours)) : undefined,
createdAt: listing.createdAt,
updatedAt: listing.updatedAt,
menuUrl: listing.menuUrl,
isFeatured: listing.isFeatured,
hasWidgetInstalled: listing.hasWidgetInstalled,
widgetSiteUrl: listing.widgetSiteUrl,
images: {
create: listing.images?.map(img => ({
id: img.id,
url: img.url,
createdAt: img.createdAt
})) || []
}
},
})
}
// 4. Etkinlikler
console.log('Etkinlikler ekleniyor...')
const events = await mockDb.getEvents(true) // Get all including past
for (const event of events) {
await prisma.event.upsert({
where: { id: event.id },
update: {},
create: {
id: event.id,
slug: event.slug,
listingId: event.listingId,
titleTr: event.titleTr,
titleEn: event.titleEn,
titleRu: event.titleRu,
descriptionTr: event.descriptionTr,
descriptionEn: event.descriptionEn,
descriptionRu: event.descriptionRu,
startDate: event.startDate,
endDate: event.endDate,
coverImage: event.coverImage,
isSponsored: event.isSponsored,
}
})
}
// 5. Koleksiyonlar
console.log('Koleksiyonlar ekleniyor...')
const collections = await mockDb.getCollections()
for (const col of collections) {
await prisma.collection.upsert({
where: { id: col.id },
update: {},
create: {
id: col.id,
slug: col.slug,
titleTr: col.titleTr,
titleEn: col.titleEn,
titleRu: col.titleRu,
descriptionTr: col.descriptionTr,
descriptionEn: col.descriptionEn,
descriptionRu: col.descriptionRu,
coverImage: col.coverImage,
listings: {
connect: col.listingIds?.map(id => ({ id })) || []
}
}
})
}
console.log('✅ Veritabanı başarıyla seed edildi!')
}
main()
.catch((e) => {
console.error(e)
process.exit(1)
})
.finally(async () => {
await prisma.$disconnect()
})