Published your App

Replit-Commit-Author: Deployment
Replit-Commit-Session-Id: 23852c00-4779-460a-9e0c-d09fee4b6c92
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 2b7650db-b0d4-4415-b9bb-cc11e719c2b7
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/23852c00-4779-460a-9e0c-d09fee4b6c92/ncMMRQ9
Replit-Commit-Deployment-Build-Id: 55db64a6-94a6-4850-b7bf-6b5712a636c3
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
sebastjanartic
2026-03-06 10:31:27 +00:00
parent 8cbb08f182
commit a23ae0bdb7
5 changed files with 420 additions and 1204 deletions
+93
View File
@@ -1,8 +1,10 @@
import fs from "fs";
import path from "path";
import { uploadToCloudinary, generateImageUrls } from "./cloudinary";
const TOKEN_PATH = path.join(process.cwd(), "server/dropbox-token.json");
const GALLERY_ROOT = "/family room/photos/oddaje/izvajalci selekcija";
const CLOUDINARY_MAP_PATH = path.join(process.cwd(), "server/cloudinary-gallery-map.json");
interface DropboxTokens {
access_token: string;
@@ -22,6 +24,23 @@ interface GalleryImage {
artist: string;
}
interface CloudinaryMap {
[fileName: string]: string;
}
function loadCloudinaryMap(): CloudinaryMap {
try {
if (fs.existsSync(CLOUDINARY_MAP_PATH)) {
return JSON.parse(fs.readFileSync(CLOUDINARY_MAP_PATH, "utf-8"));
}
} catch {}
return {};
}
function saveCloudinaryMap(map: CloudinaryMap): void {
fs.writeFileSync(CLOUDINARY_MAP_PATH, JSON.stringify(map, null, 2));
}
function extractArtistFromFileName(fileName: string): string {
if (/^DSC\d/i.test(fileName) || /^IMG[\s_]\d/i.test(fileName)) return "";
const withoutExt = fileName.replace(/\.\w+$/, "");
@@ -233,6 +252,28 @@ export async function fetchGalleryFromDropbox(): Promise<GalleryImage[]> {
return galleryCache.data;
}
const cloudinaryMap = loadCloudinaryMap();
const hasCloudinaryImages = Object.keys(cloudinaryMap).length > 0;
if (hasCloudinaryImages) {
const images: GalleryImage[] = [];
for (const [fileName, publicId] of Object.entries(cloudinaryMap)) {
const urls = generateImageUrls(publicId);
images.push({
folder: "Foto All",
fileName,
thumb: urls.thumb,
large: urls.large,
mobile: urls.mobile,
full: urls.full,
artist: extractArtistFromFileName(fileName),
});
}
galleryCache.data = images;
galleryCache.timestamp = Date.now();
return images;
}
const accessToken = await getValidAccessToken();
if (!accessToken) return [];
@@ -305,3 +346,55 @@ export async function fetchGalleryFromDropbox(): Promise<GalleryImage[]> {
return [];
}
}
export async function migrateGalleryToCloudinary(): Promise<{ uploaded: number; skipped: number; failed: number }> {
const accessToken = await getValidAccessToken();
if (!accessToken) throw new Error("Dropbox not connected");
const originalEntries = await listFolder(accessToken, ORIGINAL_FOLDER.toLowerCase());
const imgFilter = (e: any) => e[".tag"] === "file" && /\.(jpg|jpeg|png|webp|gif)$/i.test(e.name);
const originalFiles = originalEntries.filter(imgFilter);
const cloudinaryMap = loadCloudinaryMap();
let uploaded = 0;
let skipped = 0;
let failed = 0;
const batchSize = 5;
for (let i = 0; i < originalFiles.length; i += batchSize) {
const batch = originalFiles.slice(i, i + batchSize);
const links = await getTemporaryLinks(accessToken, batch.map((f: any) => f.path_lower));
const uploadPromises = batch.map(async (file: any) => {
const fileName = file.name;
if (cloudinaryMap[fileName]) {
skipped++;
return;
}
const link = links.get(file.path_lower);
if (!link) {
failed++;
return;
}
const publicId = await uploadToCloudinary(link, fileName);
if (publicId) {
cloudinaryMap[fileName] = publicId;
uploaded++;
console.log(`[cloudinary] Uploaded: ${fileName} -> ${publicId}`);
} else {
failed++;
}
});
await Promise.all(uploadPromises);
saveCloudinaryMap(cloudinaryMap);
}
galleryCache.data = [];
galleryCache.timestamp = 0;
return { uploaded, skipped, failed };
}