fix(images): Clean up image paths, compress large images, and fix openinary URLs

This commit is contained in:
Mustafa Yildiz
2026-07-18 19:28:38 +03:00
parent be94aa5155
commit f4abe0bc5b
204 changed files with 474 additions and 169 deletions
+89
View File
@@ -0,0 +1,89 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const IMAGES_DIR = path.resolve(__dirname, '../public/images');
const DATA_FILE = path.resolve(__dirname, '../lib/data.ts');
const GALLERY_FILE = path.resolve(__dirname, '../lib/gallery_data.ts');
function slugify(text) {
return text.toString().toLowerCase()
.replace(/ç/g, 'c')
.replace(/ğ/g, 'g')
.replace(/ı/g, 'i')
.replace(/ö/g, 'o')
.replace(/ş/g, 's')
.replace(/ü/g, 'u')
.replace(/[^a-z0-9.]/g, '-')
.replace(/\-\-+/g, '-')
.replace(/^-+/, '')
.replace(/-+$/, '');
}
function processDirectory(dir, mapping = []) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const oldPath = path.join(dir, entry.name);
let newName = slugify(path.parse(entry.name).name);
if (entry.isFile()) {
newName += path.extname(entry.name).toLowerCase();
}
const newPath = path.join(dir, newName);
if (entry.isDirectory()) {
// Recurse first before renaming directory
processDirectory(oldPath, mapping);
}
if (oldPath !== newPath) {
console.log(`Renaming: ${oldPath} -> ${newPath}`);
fs.renameSync(oldPath, newPath);
const oldRelative = path.relative(IMAGES_DIR, oldPath).replace(/\\/g, '/');
const newRelative = path.relative(IMAGES_DIR, newPath).replace(/\\/g, '/');
mapping.push({ old: oldRelative, new: newRelative });
}
}
return mapping;
}
console.log("Starting rename process...");
// First rename files inside directories, then rename directories
// To do this safely, we should gather all files, then directories, and rename bottom-up.
function getAllPaths(dir) {
let results = [];
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
results = results.concat(getAllPaths(fullPath));
}
results.push({ path: fullPath, isDir: entry.isDirectory(), name: entry.name });
}
return results;
}
const allPaths = getAllPaths(IMAGES_DIR).sort((a, b) => b.path.length - a.path.length); // Bottom up
const mapping = [];
for (const item of allPaths) {
const oldPath = item.path;
const dir = path.dirname(oldPath);
let newName = slugify(path.parse(item.name).name);
if (!item.isDir) {
newName += path.extname(item.name).toLowerCase();
}
const newPath = path.join(dir, newName);
if (oldPath !== newPath) {
fs.renameSync(oldPath, newPath);
console.log(`Renamed: ${item.name} -> ${newName}`);
// We will just do a global replace in files using slugify on the strings later
// It's easier than keeping track of complex path mapping.
}
}
console.log("Renaming done.");
+72
View File
@@ -0,0 +1,72 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
function slugify(text) {
return text.toString().toLowerCase()
.replace(/ç/g, 'c')
.replace(/ğ/g, 'g')
.replace(/ı/g, 'i')
.replace(/ö/g, 'o')
.replace(/ş/g, 's')
.replace(/ü/g, 'u')
// URL decode first just in case
.replace(/%20/g, ' ')
.replace(/%c3%b6/g, 'o') // ö
.replace(/%c3%bc/g, 'u') // ü
.replace(/%c3%a7/g, 'c') // ç
.replace(/%c4%b1/g, 'i') // ı
.replace(/%c5%9f/g, 's') // ş
.replace(/%c4%9f/g, 'g') // ğ
.replace(/[^a-z0-9.]/g, '-')
.replace(/\-\-+/g, '-')
.replace(/^-+/, '')
.replace(/-+$/, '');
}
function processPath(urlPath) {
// decode url first
let decoded = decodeURIComponent(urlPath);
// split by /
const parts = decoded.split('/');
const newParts = parts.map((part, index) => {
// don't slugify empty parts or fixed prefixes
if (!part) return part;
if (part === 'images' || part === 'https:' || part === '' || part === 'media.ayris.tech' || part === 'upload' || part === 'aydogan') return part;
let newName = slugify(path.parse(part).name);
const ext = path.extname(part).toLowerCase();
if (ext) {
newName += ext;
}
return newName;
});
return newParts.join('/');
}
const filesToProcess = [
path.resolve(__dirname, '../lib/data.ts'),
path.resolve(__dirname, '../lib/gallery_data.ts'),
path.resolve(__dirname, '../components/Hero.tsx'),
path.resolve(__dirname, '../components/ServicesPreview.tsx')
];
for (const file of filesToProcess) {
if (!fs.existsSync(file)) continue;
let content = fs.readFileSync(file, 'utf8');
// Replace "/images/..."
content = content.replace(/"(\/images\/[^"]+)"/g, (match, p1) => {
return `"${processPath(p1)}"`;
});
// Replace "https://media.ayris.tech/upload/aydogan/..."
content = content.replace(/"(https:\/\/media\.ayris\.tech\/upload\/aydogan\/[^"]+)"/g, (match, p1) => {
return `"${processPath(p1)}"`;
});
fs.writeFileSync(file, content);
console.log(`Updated ${file}`);
}
+78
View File
@@ -0,0 +1,78 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
// Setup environment variables manually from .env
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const envPath = path.resolve(__dirname, '../.env');
const envContent = fs.readFileSync(envPath, 'utf8');
const env = {};
envContent.split('\n').forEach(line => {
const [key, ...valueParts] = line.split('=');
if (key && valueParts.length > 0) {
let value = valueParts.join('=').trim();
if (value.startsWith('"') && value.endsWith('"')) {
value = value.substring(1, value.length - 1);
}
env[key] = value;
}
});
const IMAGES_DIR = path.resolve(__dirname, '../public/images');
const OPENINARY_API_URL = env.OPENINARY_API_URL || 'https://media.ayris.tech';
const OPENINARY_API_KEY = env.OPENINARY_API_KEY;
async function uploadFiles(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
await uploadFiles(fullPath);
} else if (entry.isFile() && /\.(png|jpg|jpeg|webp)$/i.test(entry.name)) {
const relativePath = path.relative(IMAGES_DIR, fullPath);
const folder = path.join('aydogan', path.dirname(relativePath)).replace(/\\/g, '/');
const filename = path.basename(entry.name);
console.log(`Uploading: ${relativePath} to folder: ${folder}`);
const buffer = fs.readFileSync(fullPath);
const ext = path.extname(entry.name).toLowerCase().slice(1);
const mime = ext === 'jpg' ? 'jpeg' : ext;
const blob = new Blob([buffer], { type: `image/${mime}` });
const formData = new FormData();
formData.append("files", blob, filename);
formData.append("folder", folder);
try {
const uploadRes = await fetch(`${OPENINARY_API_URL}/api/upload`, {
method: "POST",
headers: {
Authorization: `Bearer ${OPENINARY_API_KEY}`
},
body: formData,
});
if (!uploadRes.ok) {
const errorText = await uploadRes.text();
console.error(`Failed to upload ${relativePath}:`, errorText);
} else {
const data = await uploadRes.json();
console.log(`Successfully uploaded: ${data.files[0].url}`);
}
// Wait 1 second to respect rate limits (max 100 requests / 60 seconds = ~1.6 req/s max)
await new Promise(resolve => setTimeout(resolve, 1000));
} catch (error) {
console.error(`Error uploading ${relativePath}:`, error.message);
}
}
}
}
console.log('Starting upload to Openinary...');
uploadFiles(IMAGES_DIR)
.then(() => console.log('Upload finished!'))
.catch(err => console.error('Upload failed:', err));