astro_avtourist/scripts/sync-posts-from-server.ts

271 lines
No EOL
9.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<boolean> {
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<string | null> {
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<Post>();
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);