import PocketBase from 'pocketbase'; import fs from 'fs'; import path from 'path'; import https from 'https'; import http from 'http'; const SERVER_URL = 'https://avt-back.ru/'; const LOCAL_URL = 'http://127.0.0.1:8090'; const LOCAL_STORAGE_PATH = 'D:/Verstka/production/astro_avtourist/backend/pb_data/storage/pbc_1125843985'; const ADMIN_EMAIL = 'redibedi2019@gmail.com'; const ADMIN_PASSWORD = 'Stalin4444'; const localPb = new PocketBase(LOCAL_URL); interface Post { id: string; title: string; slug: string; description: string; content: string; author: string; category: string; categoryColor: string; date: string; readmeTime: string; image: string; draft: boolean; views: number; } const COLLECTION_ID = 'pbc_1125843985'; const TEMP_DIR = path.join(process.cwd(), 'temp_images'); function ensureDir(dir: string) { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } } function copyFileSync(src: string, dest: string): boolean { try { if (!fs.existsSync(src)) { return false; } ensureDir(path.dirname(dest)); fs.copyFileSync(src, dest); return true; } catch (e) { return false; } } async function downloadFile(url: string, destPath: string, token?: string): Promise { return new Promise((resolve) => { const protocol = url.startsWith('https') ? https : http; const options: https.RequestOptions = {}; if (token) { options.headers = { 'Authorization': `Bearer ${token}` }; } console.log(` Headers:`, JSON.stringify(options.headers)); const file = fs.createWriteStream(destPath); protocol.get(url, (response) => { if (response.statusCode === 404) { fs.unlinkSync(destPath); resolve(false); return; } response.pipe(file); file.on('finish', () => { file.close(); resolve(true); }); }).on('error', (err) => { fs.unlinkSync(destPath); resolve(false); }); }); } async function uploadImage(localPb: PocketBase, filePath: string): Promise { try { const formData = new FormData(); const fileBuffer = fs.readFileSync(filePath); const fileName = path.basename(filePath); const blob = new Blob([fileBuffer]); const file = new File([blob], fileName); formData.append('image', file); const record = await localPb.collection('posts').create(formData); return record.image; } catch (e) { console.error('Ошибка загрузки изображения:', e); return null; } } async function syncPosts() { console.log('🔄 Синхронизация постов с сервера на локальную БД...\n'); ensureDir(TEMP_DIR); const adminData = await localPb.admins.authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD); console.log('✅ Подключено к локальной БД\n'); const serverPb = new PocketBase(SERVER_URL); const authData = await serverPb.admins.authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD); console.log('✅ Подключено к серверу'); const token = authData.token; console.log('📥 Получение постов с сервера...'); const response = await fetch(`${SERVER_URL}api/collections/posts/records?perPage=500`, { headers: { 'Authorization': `Bearer ${token}` } }); const data = await response.json(); const serverPosts = data.items as Post[]; // Дебаг: смотрю структуру image if (serverPosts.length > 0) { console.log('\n📋 Дебаг - структура поста:'); console.log(' id:', serverPosts[0].id); console.log(' image field:', serverPosts[0].image); console.log(' slug:', serverPosts[0].slug); console.log(''); } console.log(`📊 На сервере: ${serverPosts.length} постов\n`); console.log('📥 Получение локальных постов...'); const localPosts = await localPb.collection('posts').getFullList(); const localSlugs = new Set(localPosts.map(p => p.slug)); const localPostsMap = new Map(localPosts.map(p => [p.slug, p])); console.log(`📊 Локально: ${localPosts.length} постов\n`); // Создаем карту slug -> localId для маппинга файлов const slugToLocalId = new Map(localPosts.map(p => [p.slug, p.id])); let added = 0; let skipped = 0; let imagesDownloaded = 0; for (const post of serverPosts) { if (localSlugs.has(post.slug)) { // Пост уже есть - проверяем, есть ли картинка const localPost = localPostsMap.get(post.slug); if (!localPost?.image && post.image) { console.log(`📷 Загружаю изображение для: ${post.slug}`); // Маппим server record ID -> local record ID через slug const localRecordId = slugToLocalId.get(post.slug); if (!localRecordId) { console.log(` ❌ Не найден local ID для slug: ${post.slug}\n`); continue; } const filename = post.image; // Копируем файл из server storage в local storage // Путь: D:\...\backend\pb_data\storage\pbc_1125843985\{record_id}\{filename} // Но у нас нет доступа к server storage напрямую! // Однако - мы можем скопировать из ЛОКАЛЬНОГО storage, если там есть эти файлы // Они были загружены при создании постов в локальной БД // Проверим, есть ли файл в локальном storage console.log(` Server record ID: ${post.id}`); console.log(` Local record ID: ${localRecordId}`); console.log(` Image filename: ${filename}`); // Так как файлы на сервере недоступны, попробуем скачать через другой метод // Проверим есть ли они в локальном storage const localStorageDir = path.join(LOCAL_STORAGE_PATH, localRecordId); if (fs.existsSync(localStorageDir)) { const files = fs.readdirSync(localStorageDir); console.log(` Файлы в local storage: ${files.join(', ')}`); } } skipped++; continue; } try { // Загружаем изображение если есть let localImage = ''; if (post.image) { const filename = post.image; const tempPath = path.join(TEMP_DIR, filename); const fileUrl = `${SERVER_URL}api/files/collections/posts/${post.id}/${filename}`; console.log(`📷 Скачиваю изображение для ${post.slug}...`); console.log(` URL: ${fileUrl}`); const downloaded = await downloadFile(fileUrl, tempPath, token); if (downloaded && fs.existsSync(tempPath)) { try { // Создаем запись с изображением через FormData const formData = new FormData(); const fileBuffer = fs.readFileSync(tempPath); const blob = new Blob([fileBuffer]); const file = new File([blob], filename); formData.append('image', file); formData.append('title', post.title); formData.append('slug', post.slug); formData.append('description', post.description || ''); formData.append('content', post.content || ''); formData.append('author', post.author || ''); formData.append('category', post.category || ''); formData.append('categoryColor', post.categoryColor || ''); formData.append('date', post.date || ''); formData.append('readTime', post.readmeTime || ''); formData.append('draft', String(post.draft ?? false)); formData.append('views', String(post.views ?? 0)); const newRecord = await localPb.collection('posts').create(formData); localImage = newRecord.image; fs.unlinkSync(tempPath); console.log(` ✅ Создан с изображением\n`); } catch (e: any) { console.error(` ❌ Ошибка загрузки изображения:`, e.message); // Создаем без изображения } } else { console.log(` ⚠️ Не удалось скачать изображение, создаю без него\n`); } } // Если изображение не загружено, создаем запись без него if (!localImage) { const postData = { title: post.title, slug: post.slug, description: post.description, content: post.content, author: post.author, category: post.category, categoryColor: post.categoryColor, date: post.date, readTime: post.readmeTime || '', image: '', draft: post.draft ?? false, views: post.views ?? 0, }; await localPb.collection('posts').create(postData); } console.log(`✅ Добавлен: ${post.slug}`); added++; } catch (e: any) { console.error(`❌ Ошибка добавления ${post.slug}:`, e.response?.data || e.message); } } // Очистка temps if (fs.existsSync(TEMP_DIR)) { fs.rmSync(TEMP_DIR, { recursive: true, force: true }); } console.log(`\n📊 Готово! Добавлено: ${added}, пропущено: ${skipped}, изображений загружено: ${imagesDownloaded}`); } syncPosts().catch(console.error);