"use server" import { redirect } from "next/navigation" import { mockDb } from '@/lib/mockDb' import { uploadToOpeninary } from '@/lib/openinary' import { revalidatePath } from 'next/cache' export async function submitBusinessAction(formData: FormData) { const businessName = formData.get('businessName') as string const categoryId = formData.get('categoryId') as string const neighborhoodId = formData.get('neighborhoodId') as string const address = formData.get('address') as string const phone = formData.get('phone') as string const whatsapp = formData.get('whatsapp') as string const description = formData.get('description') as string const contactName = formData.get('contactName') as string const contactEmail = formData.get('contactEmail') as string const imageFile = formData.get('imageFile') as File | null if (!businessName || !categoryId || !neighborhoodId || !address || !description || !contactName || !contactEmail) { return { error: 'Lütfen tüm zorunlu alanları doldurun.' } } let imageUrl = null if (imageFile && imageFile.size > 0) { try { imageUrl = await uploadToOpeninary(imageFile, 'submissions') } catch (e: any) { console.error('Openinary upload error:', e) return { error: `Görsel yüklenemedi: ${e.message}` } } } await mockDb.createSubmission({ businessName, categoryId, neighborhoodId, address, phone, whatsapp, description, contactName, contactEmail, imageUrl }) revalidatePath('/admin/submissions') return { success: true, error: undefined } } export async function submitContactMessageAction(formData: FormData) { const name = formData.get('name') as string const email = formData.get('email') as string const subject = formData.get('subject') as string const message = formData.get('message') as string if (!name || !email || !subject || !message) { return { error: 'Lütfen tüm alanları doldurun.' } } await mockDb.createMessage({ name, email, subject, message }) revalidatePath('/admin/messages') return { success: true, error: undefined } } export async function approveSubmissionAction(id: string) { const submission = await mockDb.getSubmissionById(id) if (!submission) return { error: 'Başvuru bulunamadı' } const slug = submission.businessName.toLowerCase() .replace(/[^a-z0-9\s-]/g, '') .replace(/\s+/g, '-') await mockDb.createListing({ slug, categoryId: submission.categoryId, neighborhoodId: submission.neighborhoodId, city: 'marmaris', nameTr: submission.businessName, nameEn: submission.businessName, nameRu: submission.businessName, descriptionTr: submission.description, descriptionEn: submission.description, descriptionRu: submission.description, address: submission.address, phone: submission.phone, whatsapp: submission.whatsapp, priceRange: 2, rating: 5.0, isLocalApproved: false, isFeatured: false, hasWidgetInstalled: false, images: submission.imageUrl ? [submission.imageUrl] : [] }) await mockDb.updateSubmissionStatus(id, 'APPROVED') revalidatePath('/admin/submissions') revalidatePath('/restaurants') revalidatePath('/aparts') revalidatePath('/businesses') return { success: true, error: undefined } } export async function rejectSubmissionAction(id: string) { await mockDb.updateSubmissionStatus(id, 'REJECTED') revalidatePath('/admin/submissions') return { success: true, error: undefined } } export async function deleteListingAction(id: string) { await mockDb.deleteListing(id) revalidatePath('/admin/listings') revalidatePath('/restaurants') revalidatePath('/aparts') revalidatePath('/businesses') return { success: true, error: undefined } } export async function restoreListingAction(id: string) { await mockDb.restoreListing(id) revalidatePath('/admin/trash') revalidatePath('/admin/listings') return { success: true, error: undefined } } export async function hardDeleteListingAction(id: string) { try { await mockDb.hardDeleteListing(id) revalidatePath('/admin/trash') return { success: true, error: undefined } } catch (err: any) { if (err.message?.includes('RESTRICT') || err.message?.includes('Foreign key constraint')) { return { success: false, error: 'Silme başarısız: Bu mekana bağlı etkinlik (Event) veya kayıtlar var.' } } return { success: false, error: err.message || 'Bilinmeyen bir hata oluştu.' } } } export async function markMessageReadAction(id: string) { await mockDb.markMessageAsRead(id) revalidatePath('/admin/messages') return { success: true, error: undefined } } export async function createOrUpdateListingAction(formData: FormData) { const id = formData.get('id') as string | null const slug = formData.get('slug') as string const categoryId = formData.get('categoryId') as string const neighborhoodId = formData.get('neighborhoodId') as string const nameTr = formData.get('nameTr') as string const nameEn = formData.get('nameEn') as string const nameRu = formData.get('nameRu') as string const descriptionTr = formData.get('descriptionTr') as string const descriptionEn = formData.get('descriptionEn') as string const descriptionRu = formData.get('descriptionRu') as string const address = formData.get('address') as string const phone = formData.get('phone') as string || null const whatsapp = formData.get('whatsapp') as string || null const website = formData.get('website') as string || null const instagram = formData.get('instagram') as string || null const priceRange = parseInt(formData.get('priceRange') as string) const rating = formData.get('rating') ? parseFloat(formData.get('rating') as string) : null const isLocalApproved = formData.get('isLocalApproved') === 'true' const isFeatured = formData.get('isFeatured') === 'true' const latitude = formData.get('latitude') ? parseFloat(formData.get('latitude') as string) : null const longitude = formData.get('longitude') ? parseFloat(formData.get('longitude') as string) : null const openingHoursStr = formData.get('openingHours') as string const openingHours = openingHoursStr ? { all: openingHoursStr } : null // Handle images const images: string[] = [] const imageFile1 = formData.get('imageFile1') as File | null const imageUrl1 = formData.get('imageUrl1') as string | null const imageFile2 = formData.get('imageFile2') as File | null const imageUrl2 = formData.get('imageUrl2') as string | null // Process first image if (imageFile1 && imageFile1.size > 0) { try { const url = await uploadToOpeninary(imageFile1, `listings/${slug}`) images.push(url) } catch (e: any) { console.error('Image 1 upload error:', e) return { success: false, error: `1. Görsel yüklenemedi: ${e.message}` } } } else if (imageUrl1) { images.push(imageUrl1) } // Process second image if (imageFile2 && imageFile2.size > 0) { try { const url = await uploadToOpeninary(imageFile2, `listings/${slug}`) images.push(url) } catch (e: any) { console.error('Image 2 upload error:', e) return { success: false, error: `2. Görsel yüklenemedi: ${e.message}` } } } else if (imageUrl2) { images.push(imageUrl2) } const data = { slug, categoryId, neighborhoodId, city: 'marmaris', nameTr, nameEn, nameRu, descriptionTr, descriptionEn, descriptionRu, address, phone, whatsapp, website, instagram, priceRange, rating, isLocalApproved, isFeatured, latitude, longitude, openingHours, images, hasWidgetInstalled: false } try { if (id && id !== 'new') { await mockDb.updateListing(id, data) } else { await mockDb.createListing(data) } revalidatePath('/admin/listings') revalidatePath('/restaurants') revalidatePath('/aparts') revalidatePath('/businesses') return { success: true, error: undefined } } catch (err: any) { return { success: false, error: err.message || 'Mekan kaydedilemedi.' } } } // Blog Actions export async function deleteBlogPostAction(id: string) { await mockDb.deleteBlogPost(id) revalidatePath('/admin/blog') revalidatePath('/blog') return { success: true, error: undefined } } export async function createOrUpdateBlogPostAction(formData: FormData) { const id = formData.get('id') as string | null const slug = formData.get('slug') as string const titleTr = formData.get('titleTr') as string const titleEn = formData.get('titleEn') as string const titleRu = formData.get('titleRu') as string const contentTr = formData.get('contentTr') as string const contentEn = formData.get('contentEn') as string const contentRu = formData.get('contentRu') as string const tagsStr = formData.get('tags') as string || '' const tags = tagsStr.split(',').map(t => t.trim()).filter(Boolean) const relatedListingIdsStr = formData.get('relatedListingIds') as string || '' const relatedListingIds = relatedListingIdsStr.split(',').map(i => i.trim()).filter(Boolean) const isPublished = formData.get('isPublished') === 'true' const publishedAt = isPublished ? new Date() : null // Handle Cover Image const coverImageFile = formData.get('coverImageFile') as File | null let coverImage = formData.get('coverImageUrl') as string | null || null if (coverImageFile && coverImageFile.size > 0) { try { coverImage = await uploadToOpeninary(coverImageFile, `blog/${slug}`) } catch (e: any) { console.error('Blog cover upload error:', e) return { success: false, error: `Kapak görseli yüklenemedi: ${e.message}` } } } const data = { slug, titleTr, titleEn, titleRu, contentTr, contentEn, contentRu, coverImage, tags, relatedListingIds, publishedAt } try { if (id && id !== 'new') { await mockDb.updateBlogPost(id, data) } else { await mockDb.createBlogPost(data) } revalidatePath('/admin/blog') revalidatePath('/blog') revalidatePath(`/blog/${slug}`) return { success: true, error: undefined } } catch (err: any) { return { success: false, error: err.message || 'Yazı kaydedilemedi.' } } } // Collections Actions export async function deleteCollectionAction(id: string) { await mockDb.deleteCollection(id) revalidatePath('/admin/collections') revalidatePath('/collections') return { success: true, error: undefined } } export async function createOrUpdateCollectionAction(formData: FormData) { const id = formData.get('id') as string | null const slug = formData.get('slug') as string const titleTr = formData.get('titleTr') as string const titleEn = formData.get('titleEn') as string const titleRu = formData.get('titleRu') as string const descriptionTr = formData.get('descriptionTr') as string const descriptionEn = formData.get('descriptionEn') as string const descriptionRu = formData.get('descriptionRu') as string const listingIdsStr = formData.get('listingIds') as string || '' const listingIds = listingIdsStr.split(',').map(i => i.trim()).filter(Boolean) // Handle Cover Image const coverImageFile = formData.get('coverImageFile') as File | null let coverImage = formData.get('coverImageUrl') as string | null || null if (coverImageFile && coverImageFile.size > 0) { try { coverImage = await uploadToOpeninary(coverImageFile, `collections/${slug}`) } catch (e: any) { console.error('Collection cover upload error:', e) return { success: false, error: `Kapak görseli yüklenemedi: ${e.message}` } } } const data = { slug, titleTr, titleEn, titleRu, descriptionTr, descriptionEn, descriptionRu, coverImage, listingIds } try { if (id && id !== 'new') { await mockDb.updateCollection(id, data) } else { await mockDb.createCollection(data) } revalidatePath('/admin/collections') revalidatePath('/collections') revalidatePath(`/collection/${slug}`) return { success: true, error: undefined } } catch (err: any) { return { success: false, error: err.message || 'Koleksiyon kaydedilemedi.' } } } // Events Actions (Phase 3) export async function deleteEventAction(id: string) { await mockDb.deleteEvent(id) revalidatePath('/admin/events') revalidatePath('/events') return { success: true, error: undefined } } export async function createOrUpdateEventAction(formData: FormData) { const id = formData.get('id') as string | null const slug = formData.get('slug') as string const listingId = formData.get('listingId') as string | null || null const titleTr = formData.get('titleTr') as string const titleEn = formData.get('titleEn') as string const titleRu = formData.get('titleRu') as string const descriptionTr = formData.get('descriptionTr') as string const descriptionEn = formData.get('descriptionEn') as string const descriptionRu = formData.get('descriptionRu') as string const startDateStr = formData.get('startDate') as string const endDateStr = formData.get('endDate') as string | null const isSponsored = formData.get('isSponsored') === 'true' const startDate = new Date(startDateStr) const endDate = endDateStr ? new Date(endDateStr) : null // Handle Cover Image const coverImageFile = formData.get('coverImageFile') as File | null let coverImage = formData.get('coverImageUrl') as string | null || null if (coverImageFile && coverImageFile.size > 0) { try { coverImage = await uploadToOpeninary(coverImageFile, `events/${slug}`) } catch (e: any) { console.error('Event cover upload error:', e) return { success: false, error: `Kapak görseli yüklenemedi: ${e.message}` } } } const data = { slug, listingId, titleTr, titleEn, titleRu, descriptionTr, descriptionEn, descriptionRu, startDate, endDate, coverImage, isSponsored } try { if (id && id !== 'new') { await mockDb.updateEvent(id, data) } else { await mockDb.createEvent(data) } revalidatePath('/admin/events') revalidatePath('/events') revalidatePath(`/event/${slug}`) return { success: true, error: undefined } } catch (err: any) { return { success: false, error: err.message || 'Etkinlik kaydedilemedi.' } } } // Neighborhood Actions export async function deleteNeighborhoodAction(id: string) { try { const listings = await mockDb.getListings({ neighborhoodId: id }) if (listings && listings.length > 0) { return { success: false, error: `Silme başarısız: Bu mahalleye kayıtlı ${listings.length} adet mekan var.` } } await mockDb.deleteNeighborhood(id) revalidatePath('/admin/neighborhoods') return { success: true, error: undefined } } catch (err: any) { if (err.message?.includes('RESTRICT') || err.message?.includes('Foreign key constraint')) { return { success: false, error: 'Silme başarısız: Bu mahalleye bağlı gizli (silinmiş veya arşivlenmiş) mekanlar veya etkinlikler var.' } } return { success: false, error: err.message || 'Bilinmeyen bir hata oluştu.' } } } export async function createOrUpdateNeighborhoodAction(formData: FormData) { const id = formData.get('id') as string | null const slug = formData.get('slug') as string const nameTr = formData.get('nameTr') as string const nameEn = formData.get('nameEn') as string const nameRu = formData.get('nameRu') as string if (!slug || !nameTr || !nameEn || !nameRu) { return { error: 'Zorunlu alanları doldurun.' } } const data = { slug, nameTr, nameEn, nameRu, } try { if (id && id !== 'new') { await mockDb.updateNeighborhood(id, data) } else { await mockDb.createNeighborhood(data) } revalidatePath('/admin/neighborhoods') return { success: true, error: undefined } } catch (err: any) { return { success: false, error: err.message || 'Mahalle kaydedilemedi.' } } } // AI Itinerary Planner Actions (Phase 3) import crypto from 'crypto' async function generateMockItinerary(days: number, style: string, neighborhoodSlugs: string[]) { const allListings = await mockDb.getListings() const matchingListings = allListings.filter(l => !l.deletedAt && l.isLocalApproved && (neighborhoodSlugs.length === 0 || (l.neighborhood && neighborhoodSlugs.includes(l.neighborhood.slug))) ) const pool = matchingListings.length > 0 ? matchingListings : allListings.filter(l => !l.deletedAt && l.isLocalApproved) let md = `# marmaris local kişisel gezi rotası 🌴\n\n` md += `**gün sayısı:** ${days} gün | **gezi tarzı:** ${style === 'gastronomy' ? 'gurme' : style === 'relaxation' ? 'dinlenme' : 'macera'} | **keşif bölgeleri:** ${neighborhoodSlugs.join(', ')}\n\n` md += `bu rota, marmaris local topluluğu tarafından onaylanmış yerel işletmeler temel alınarak oluşturulmuştur.\n\n---\n\n` for (let day = 1; day <= days; day++) { md += `## 🗓️ gün ${day}\n\n` const dayListings = [...pool].sort(() => 0.5 - Math.random()).slice(0, 3) if (dayListings.length >= 1) { md += `### 🌅 sabah: kahvaltı ve başlangıç\n` md += `güne yerel onaylı **[${dayListings[0].nameTr}](/${dayListings[0].category?.slug || 'isletme'}/${dayListings[0].slug})** işletmesinde başlayın. \n` md += `> **yerel ipucu:** ${dayListings[0].descriptionTr}\n\n` } if (dayListings.length >= 2) { md += `### ☀️ öğle: keşif zamanı\n` md += `öğleden sonra **[${dayListings[1].nameTr}](/${dayListings[1].category?.slug || 'isletme'}/${dayListings[1].slug})** mekanına uğrayın ve çevreyi keşfedin.\n` md += `> **editör notu:** ${dayListings[1].address}\n\n` } if (dayListings.length >= 3) { md += `### 🌌 akşam: gün batımı ve akşam yemeği\n` md += `akşamı şık bir akşam yemeğiyle taçlandırın: **[${dayListings[2].nameTr}](/${dayListings[2].category?.slug || 'isletme'}/${dayListings[2].slug})**.\n` md += `> **özel detaylar:** rating: ★${dayListings[2].rating} • tel: ${dayListings[2].phone || 'belirtilmemiş'}\n\n` } md += `---\n\n` } md += `*not: seyahatiniz boyunca yerel rehberimizdeki işletmeleri ziyaret etmeyi ve favorilerinize eklemeyi unutmayın!*` return { content: md, listingIds: pool.map(l => l.id) } } export async function generateItineraryAction(days: number, style: string, neighborhoodSlugs: string[]) { const sorted = [...neighborhoodSlugs].sort() const paramsHash = crypto.createHash('md5').update(JSON.stringify({ days, style, neighborhoodSlugs: sorted })).digest('hex') const existing = await mockDb.getItineraryByHash(paramsHash) if (existing) { return { success: true, id: existing.id } } // Generate new program const { content, listingIds } = await generateMockItinerary(days, style, sorted) const newItin = await mockDb.createItinerary({ paramsHash, params: { days, style, neighborhoodSlugs: sorted }, content, listingIds }) return { success: true, id: newItin.id } } export async function createOrUpdateCategoryAction(formData: FormData) { const id = formData.get('id') as string const slug = formData.get('slug') as string const nameTr = formData.get('nameTr') as string const nameEn = formData.get('nameEn') as string const nameRu = formData.get('nameRu') as string if (!slug || !nameTr || !nameEn || !nameRu) { throw new Error('Eksik alanlar var') } if (id && id !== 'new') { await mockDb.updateCategory(id, { slug, nameTr, nameEn, nameRu }) } else { await mockDb.createCategory({ slug, nameTr, nameEn, nameRu }) } revalidatePath('/admin/categories') redirect('/tr/admin/categories') } export async function deleteCategoryAction(formData: FormData) { const id = formData.get('id') as string if (!id) return await mockDb.deleteCategory(id) revalidatePath('/admin/categories') } export async function deleteSubmissionAction(id: string) { await mockDb.deleteSubmission(id) revalidatePath('/admin/submissions') return { success: true, error: undefined } } export async function deleteMessageAction(id: string) { await mockDb.deleteMessage(id) revalidatePath('/admin/messages') return { success: true, error: undefined } } export async function searchListingsAction(query: string) { if (!query || query.length < 2) return [] const allListings = await mockDb.getListings() const q = query.toLowerCase() const results = allListings.filter(l => !l.deletedAt && ( l.nameTr.toLowerCase().includes(q) || l.nameEn?.toLowerCase().includes(q) || l.nameRu?.toLowerCase().includes(q) ) ).slice(0, 5) // Return max 5 suggestions return results.map(l => ({ id: l.id, nameTr: l.nameTr, nameEn: l.nameEn, nameRu: l.nameRu, slug: l.slug, categorySlug: l.category?.slug || 'restoran' })) } export async function saveWidgetPartnerAction(formData: FormData) { const id = formData.get('id') as string | null const name = formData.get('name') as string const url = formData.get('url') as string const neighborhoodSlug = formData.get('neighborhoodSlug') as string | null const isActive = formData.get('isActive') === 'true' const isHidden = formData.get('isHidden') === 'true' if (!name || !url) { return { error: 'Lütfen zorunlu alanları doldurun.' } } const data = { name, url, neighborhoodSlug: neighborhoodSlug || null, isActive, isHidden } try { if (id && id !== 'new') { await mockDb.updateWidgetPartner(id, data) } else { await mockDb.createWidgetPartner(data) } revalidatePath('/admin/widget-partners') return { success: true, error: undefined } } catch (error: any) { return { error: error.message || 'Bir hata oluştu.' } } } export async function deleteWidgetPartnerAction(id: string) { await mockDb.deleteWidgetPartner(id) revalidatePath('/admin/widget-partners') return { success: true, error: undefined } }