diff --git a/app/admin/gallery/page.tsx b/app/admin/gallery/page.tsx index 7b66ce5..f75ab1d 100644 --- a/app/admin/gallery/page.tsx +++ b/app/admin/gallery/page.tsx @@ -4,7 +4,7 @@ import { redirect } from 'next/navigation'; import { Trash2, Plus, Image as ImageIcon } from 'lucide-react'; import { addGalleryItem, deleteGalleryItem } from '../actions'; import { revalidatePath } from 'next/cache'; -import { uploadImage } from '@/lib/cloudinary'; +import { uploadImage } from '@/lib/openinary'; export default async function GalleryAdminPage() { const session = await getSession(); diff --git a/app/admin/hero/page.tsx b/app/admin/hero/page.tsx index bbc972c..94ed9ee 100644 --- a/app/admin/hero/page.tsx +++ b/app/admin/hero/page.tsx @@ -4,7 +4,7 @@ import { redirect } from 'next/navigation'; import { Image as ImageIcon, Video, Upload } from 'lucide-react'; import { updateHeroMedia } from '../actions'; import { revalidatePath } from 'next/cache'; -import { uploadImage } from '@/lib/cloudinary'; +import { uploadImage } from '@/lib/openinary'; export default async function HeroAdminPage() { const session = await getSession(); diff --git a/app/admin/services/page.tsx b/app/admin/services/page.tsx index b67c930..b396863 100644 --- a/app/admin/services/page.tsx +++ b/app/admin/services/page.tsx @@ -4,7 +4,7 @@ import { redirect } from 'next/navigation'; import { Trash2, Plus, Briefcase, Edit, X } from 'lucide-react'; import { addServiceItem, deleteServiceItem, updateServiceItem } from '../actions'; import { revalidatePath } from 'next/cache'; -import { uploadImage } from '@/lib/cloudinary'; +import { uploadImage } from '@/lib/openinary'; import Link from 'next/link'; export default async function ServicesAdminPage({ searchParams }: { searchParams: Promise<{ edit?: string }> }) { diff --git a/app/admin/settings/page.tsx b/app/admin/settings/page.tsx index 04bc5f2..8a14c89 100644 --- a/app/admin/settings/page.tsx +++ b/app/admin/settings/page.tsx @@ -4,7 +4,7 @@ import { redirect } from 'next/navigation' import { Save } from 'lucide-react'; import { updateSiteSettings } from '../actions'; import { revalidatePath } from 'next/cache'; -import { uploadImage } from '@/lib/cloudinary'; +import { uploadImage } from '@/lib/openinary'; export default async function SettingsAdminPage() { const session = await getSession(); diff --git a/lib/cloudinary.ts b/lib/cloudinary.ts deleted file mode 100644 index b527c58..0000000 --- a/lib/cloudinary.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { v2 as cloudinary } from 'cloudinary'; - -export async function uploadImage(file: File): Promise { - const arrayBuffer = await file.arrayBuffer(); - const buffer = Buffer.from(arrayBuffer); - - return new Promise((resolve, reject) => { - const uploadStream = cloudinary.uploader.upload_stream( - { folder: 'moybeach', resource_type: 'auto' }, - (error, result) => { - if (error) { - reject(error); - } else { - resolve(result!.secure_url); - } - } - ); - - uploadStream.end(buffer); - }); -} diff --git a/lib/openinary.ts b/lib/openinary.ts new file mode 100644 index 0000000..7d5bdad --- /dev/null +++ b/lib/openinary.ts @@ -0,0 +1,25 @@ +export async function uploadToOpeninary(file: File, folder: string = "uploads") { + const formData = new FormData(); + formData.append("files", file); + formData.append("folder", folder); + + const res = await fetch(`${process.env.OPENINARY_API_URL}/api/upload`, { + method: "POST", + headers: { Authorization: `Bearer ${process.env.OPENINARY_API_KEY}` }, + body: formData, + }); + + if (!res.ok) { + const text = await res.text(); + console.error("Openinary upload error:", text); + throw new Error("Upload başarısız"); + } + + const data = await res.json(); + return data.files[0]; +} + +export async function uploadImage(file: File): Promise { + const fileData = await uploadToOpeninary(file, "moybeach"); + return `${process.env.NEXT_PUBLIC_OPENINARY_URL}${fileData.url}`; +} diff --git a/next.config.ts b/next.config.ts index 56ad25e..6851fff 100644 --- a/next.config.ts +++ b/next.config.ts @@ -12,6 +12,10 @@ const nextConfig: NextConfig = { protocol: "https", hostname: "res.cloudinary.com", }, + { + protocol: "https", + hostname: "media.ayris.tech", + }, ], }, experimental: { diff --git a/package-lock.json b/package-lock.json index 8986cc8..a138e98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,6 @@ "dependencies": { "@prisma/client": "^6.4.1", "bcryptjs": "^3.0.3", - "cloudinary": "^2.10.0", "framer-motion": "^12.40.0", "jose": "^6.2.3", "lucide-react": "^1.17.0", @@ -3283,18 +3282,6 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, - "node_modules/cloudinary": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.10.0.tgz", - "integrity": "sha512-sY09kYg7wprkndAOjZBAYqFZqwL+SxnEGcAvksOvFA+5upnFn949UjkEkHKNSwkBtW/xRDd0p6NgbSXZcxkI3w==", - "license": "MIT", - "dependencies": { - "lodash": "^4.17.23" - }, - "engines": { - "node": ">=9" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -5631,12 +5618,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", diff --git a/package.json b/package.json index 3c648c1..7534b66 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "dependencies": { "@prisma/client": "^6.4.1", "bcryptjs": "^3.0.3", - "cloudinary": "^2.10.0", "framer-motion": "^12.40.0", "jose": "^6.2.3", "lucide-react": "^1.17.0", diff --git a/scripts/migrate-to-openinary.ts b/scripts/migrate-to-openinary.ts new file mode 100644 index 0000000..dd0a723 --- /dev/null +++ b/scripts/migrate-to-openinary.ts @@ -0,0 +1,134 @@ +import { PrismaClient } from '@prisma/client'; +import fs from 'fs'; +import path from 'path'; + +const prisma = new PrismaClient(); + +const OPENINARY_API_URL = process.env.OPENINARY_API_URL; +const OPENINARY_API_KEY = process.env.OPENINARY_API_KEY; +const NEXT_PUBLIC_OPENINARY_URL = process.env.NEXT_PUBLIC_OPENINARY_URL; + +async function downloadImageAsBlob(url: string, filename: string): Promise { + const response = await fetch(url); + if (!response.ok) throw new Error(`Failed to download ${url}`); + const arrayBuffer = await response.arrayBuffer(); + + // Create a Blob from the ArrayBuffer + return new Blob([arrayBuffer], { type: response.headers.get('content-type') || 'application/octet-stream' }); +} + +async function uploadToOpeninary(blob: Blob, filename: string): Promise { + const formData = new FormData(); + // Next.js node fetch environment allows passing Blob to FormData + formData.append('files', blob, filename); + formData.append('folder', 'moybeach'); + + const res = await fetch(`${OPENINARY_API_URL}/api/upload`, { + method: 'POST', + headers: { Authorization: `Bearer ${OPENINARY_API_KEY}` }, + body: formData, + }); + + if (!res.ok) { + const text = await res.text(); + console.error('Openinary upload error:', text); + throw new Error('Upload başarısız'); + } + + const data = await res.json(); + return `${NEXT_PUBLIC_OPENINARY_URL}${data.files[0].url}`; +} + +async function migrateImage(url: string | null): Promise { + if (!url || !url.includes('cloudinary.com')) return url; + + try { + console.log(`Migrating: ${url}`); + const filename = url.split('/').pop() || 'image.jpg'; + const blob = await downloadImageAsBlob(url, filename); + const newUrl = await uploadToOpeninary(blob, filename); + console.log(`Success: ${newUrl}`); + return newUrl; + } catch (error) { + console.error(`Error migrating ${url}:`, error); + return url; + } +} + +async function main() { + if (!OPENINARY_API_URL || !OPENINARY_API_KEY) { + console.error('Missing Openinary Env Variables!'); + process.exit(1); + } + + console.log('Starting migration from Cloudinary to Openinary...'); + + // 1. Gallery + const galleries = await prisma.gallery.findMany(); + for (const item of galleries) { + if (item.imageUrl.includes('cloudinary.com')) { + const newUrl = await migrateImage(item.imageUrl); + if (newUrl && newUrl !== item.imageUrl) { + await prisma.gallery.update({ where: { id: item.id }, data: { imageUrl: newUrl } }); + } + } + } + + // 2. Service + const services = await prisma.service.findMany(); + for (const item of services) { + if (item.iconUrl && item.iconUrl.includes('cloudinary.com')) { + const newUrl = await migrateImage(item.iconUrl); + if (newUrl && newUrl !== item.iconUrl) { + await prisma.service.update({ where: { id: item.id }, data: { iconUrl: newUrl } }); + } + } + } + + // 3. HeroMedia + const heroMedias = await prisma.heroMedia.findMany(); + for (const item of heroMedias) { + if (item.url.includes('cloudinary.com')) { + const newUrl = await migrateImage(item.url); + if (newUrl && newUrl !== item.url) { + await prisma.heroMedia.update({ where: { id: item.id }, data: { url: newUrl } }); + } + } + } + + // 4. SiteSettings + const siteSettings = await prisma.siteSettings.findMany(); + for (const item of siteSettings) { + let changed = false; + let newLogoUrl = item.logoUrl; + let newOgImage = item.ogImage; + + if (item.logoUrl && item.logoUrl.includes('cloudinary.com')) { + newLogoUrl = await migrateImage(item.logoUrl) ?? item.logoUrl; + if (newLogoUrl !== item.logoUrl) changed = true; + } + + if (item.ogImage && item.ogImage.includes('cloudinary.com')) { + newOgImage = await migrateImage(item.ogImage) ?? item.ogImage; + if (newOgImage !== item.ogImage) changed = true; + } + + if (changed) { + await prisma.siteSettings.update({ + where: { id: item.id }, + data: { logoUrl: newLogoUrl, ogImage: newOgImage } + }); + } + } + + console.log('Migration completed!'); +} + +main() + .catch(e => { + console.error(e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/scripts/upload-to-cloudinary.js b/scripts/upload-to-cloudinary.js deleted file mode 100644 index c38da7e..0000000 --- a/scripts/upload-to-cloudinary.js +++ /dev/null @@ -1,47 +0,0 @@ -const cloudinary = require('cloudinary').v2; -const fs = require('fs'); -const path = require('path'); - -cloudinary.config({ - cloud_name: 'du7xohbct', - api_key: '525922573613433', - api_secret: 'cJ0NDcaoQhSTAxBMv6jNMFupt3k' -}); - -const uploadImages = async () => { - const foldersToUpload = ['kitehotel', 'kitesurf']; - const results = {}; - - for (const folderName of foldersToUpload) { - const publicDir = path.join(__dirname, '..', 'public', folderName); - - if (!fs.existsSync(publicDir)) { - console.log(`Directory does not exist: ${publicDir}`); - continue; - } - - const files = fs.readdirSync(publicDir); - const images = files.filter(file => file.endsWith('.jpg') || file.endsWith('.jpeg') || file.endsWith('.png') || file.endsWith('.webp')); - - for (const file of images) { - console.log(`Uploading ${folderName}/${file}...`); - try { - const filePath = path.join(publicDir, file); - const result = await cloudinary.uploader.upload(filePath, { - folder: `moygrup/${folderName}`, - use_filename: true, - unique_filename: false - }); - console.log(`Uploaded ${file} -> ${result.secure_url}`); - results[`${folderName}/${file}`] = result.secure_url; - } catch (error) { - console.error(`Error uploading ${folderName}/${file}:`, error); - } - } - } - - console.log('\n--- Upload Results ---'); - console.log(JSON.stringify(results, null, 2)); -}; - -uploadImages();