# app/utils/pouch.coffee class PouchDBService constructor: (options = {}) -> {@localDbName, @remoteDbUrl, @appVersion} = options @localDb = null @remoteDb = null @syncHandler = null @initialized = false @syncStatus = 'disconnected' @useLocalDB = false # Отключаем локальную БД временно init: -> return Promise.resolve() if @initialized try log '🔄 Инициализация PouchDB сервиса' # Отключаем локальную базу в браузере if @useLocalDB @localDb = new PouchDB(@localDbName or 'braer_color_cache') log '✅ Локальная PouchDB инициализирована' else log '⚠️ Локальная PouchDB отключена' # Инициализация удаленной базы @remoteDb = new PouchDB(@remoteDbUrl, { skip_setup: true # Не создавать автоматически fetch: (url, opts) => opts.credentials = 'include' opts.headers ?= {} # Добавляем базовую аутентификацию если нужно PouchDB.fetch(url, opts) }) # Проверяем и создаем базу если нужно await @ensureDatabaseExists() log '✅ Удаленная CouchDB подключена' # Создаем дизайн-документы await @ensureDesignDocuments() log '✅ Дизайн-документы проверены/созданы' # Настройка синхронизации (если локальная БД включена) if @useLocalDB @setupSync() @initialized = true log '🎉 PouchDB сервис полностью инициализирован' return Promise.resolve() catch error log '❌ Критическая ошибка инициализации PouchDB: ' + error return Promise.reject(error) ensureDatabaseExists: -> try # Пробуем получить информацию о базе info = await @remoteDb.info() log '📊 Информация о базе: ' + JSON.stringify(info) return true catch error if error.status == 404 log '📦 База данных не найдена, создаем новую...' # Создаем базу через отдельный запрос await @createDatabase() return true else log '❌ Ошибка подключения к базе: ' + error throw error createDatabase: -> try # Создаем базу через PUT запрос response = await fetch(@remoteDbUrl, { method: 'PUT' headers: { 'Content-Type': 'application/json' } }) if response.ok log '✅ База данных успешно создана' # Переинициализируем подключение @remoteDb = new PouchDB(@remoteDbUrl, { skip_setup: false }) else throw new Error('Не удалось создать базу: ' + response.status) catch error log '❌ Ошибка создания базы данных: ' + error throw error ensureDesignDocuments: -> designDocs = require 'app/design/site' promises = [] for name, doc of designDocs.designDocs promises.push(@ensureDesignDocument(doc)) await Promise.all(promises) log '✅ Все дизайн-документы проверены' ensureDesignDocument: (designDoc) -> try # Пробуем получить существующий дизайн-документ existingDoc = await @remoteDb.get(designDoc._id) # Сравниваем версии или хеши для определения необходимости обновления if @needsDesignDocUpdate(existingDoc, designDoc) designDoc._rev = existingDoc._rev await @remoteDb.put(designDoc) log '✅ Дизайн-документ обновлен: ' + designDoc._id else log '⚠️ Дизайн-документ уже актуален: ' + designDoc._id catch error if error.status == 404 # Дизайн-документ не существует, создаем новый await @remoteDb.put(designDoc) log '✅ Дизайн-документ создан: ' + designDoc._id else log '❌ Ошибка работы с дизайн-документом ' + designDoc._id + ': ' + error throw error needsDesignDocUpdate: (existingDoc, newDoc) -> # Простая проверка: сравниваем хеш views existingViewsHash = JSON.stringify(existingDoc.views) newViewsHash = JSON.stringify(newDoc.views) return existingViewsHash != newViewsHash setupSync: -> return unless @useLocalDB and @localDb and @remoteDb @syncHandler = PouchDB.sync(@localDb, @remoteDb, { live: true, retry: true, batch_size: 50, batches_limit: 10, filter: (doc) => @shouldSyncDocument(doc) }) .on 'change', (info) => log '📡 Синхронизация: данные изменены' @syncStatus = 'syncing' .on 'paused', (err) => log '⏸️ Синхронизация приостановлена' @syncStatus = 'paused' .on 'active', => log '🔄 Синхронизация активна' @syncStatus = 'active' .on 'denied', (err) => log '🚫 Доступ запрещен: ' + err @syncStatus = 'denied' .on 'complete', (info) => log '✅ Синхронизация завершена' @syncStatus = 'complete' .on 'error', (err) => log '❌ Ошибка синхронизации: ' + err @syncStatus = 'error' @handleSyncError(err) shouldSyncDocument: (doc) -> return true if doc.type in ['product', 'category', 'domain_settings', 'hero_slide'] return true if doc.type in ['blog_article', 'route', 'user_settings'] if doc.type in ['order', 'user_data', 'cart'] return doc.userId == @getCurrentUserId() return false getCurrentUserId: -> userData = localStorage.getItem('user') if userData try user = JSON.parse(userData) return user.id catch return 'anonymous' return 'anonymous' handleSyncError: (error) -> log '🔄 Обработка ошибки синхронизации: ' + error if error.status in [408, 429, 500, 502, 503, 504] log '⏳ Временная ошибка, повтор через 5 секунд...' setTimeout (=> @setupSync() ), 5000 else if error.status == 401 log '🔐 Ошибка аутентификации, требуется перелогин' document.dispatchEvent(new CustomEvent('auth-required')) # Основные методы работы с данными getDocument: (docId) -> @ensureInit() try if @useLocalDB and @localDb doc = await @localDb.get(docId) log '📄 Документ получен из локальной базы: ' + docId return doc else doc = await @remoteDb.get(docId) log '📄 Документ получен из удаленной базы: ' + docId return doc catch error log '❌ Ошибка получения документа: ' + error throw error saveDocument: (doc) -> @ensureInit() try if @useLocalDB and @localDb result = await @localDb.put(doc) log '💾 Документ сохранен локально: ' + doc._id return result else result = await @remoteDb.put(doc) log '💾 Документ сохранен в удаленной базе: ' + doc._id return result catch error log '❌ Ошибка сохранения документа: ' + error throw error bulkDocs: (docs) -> @ensureInit() try if @useLocalDB and @localDb result = await @localDb.bulkDocs(docs) log '📦 Пакетное сохранение документов в локальную базу: ' + docs.length return result else result = await @remoteDb.bulkDocs(docs) log '📦 Пакетное сохранение документов в удаленную базу: ' + docs.length return result catch error log '❌ Ошибка пакетного сохранения: ' + error throw error queryView: (designDoc, viewName, options = {}) -> @ensureInit() try if @useLocalDB and @localDb result = await @localDb.query(designDoc + '/' + viewName, options) log '🔍 Выполнен запрос к локальной view: ' + designDoc + '/' + viewName return result else result = await @remoteDb.query(designDoc + '/' + viewName, options) log '🔍 Выполнен запрос к удаленной view: ' + designDoc + '/' + viewName return result catch error log '❌ Ошибка запроса к view: ' + error throw error allDocs: (options = {}) -> @ensureInit() try if @useLocalDB and @localDb return await @localDb.allDocs(options) else return await @remoteDb.allDocs(options) catch error log '❌ Ошибка allDocs: ' + error throw error putAttachment: (docId, attachmentId, attachment, type) -> @ensureInit() try if @useLocalDB and @localDb result = await @localDb.putAttachment(docId, attachmentId, attachment, type) log '📎 Вложение добавлено в локальную базу: ' + docId + '/' + attachmentId return result else result = await @remoteDb.putAttachment(docId, attachmentId, attachment, type) log '📎 Вложение добавлено в удаленную базу: ' + docId + '/' + attachmentId return result catch error log '❌ Ошибка добавления вложения: ' + error throw error getAttachment: (docId, attachmentId) -> @ensureInit() try if @useLocalDB and @localDb attachment = await @localDb.getAttachment(docId, attachmentId) log '📎 Вложение получено из локальной базы: ' + docId + '/' + attachmentId return attachment else attachment = await @remoteDb.getAttachment(docId, attachmentId) log '📎 Вложение получено из удаленной базы: ' + docId + '/' + attachmentId return attachment catch error log '❌ Ошибка получения вложения: ' + error throw error removeDocument: (docId) -> @ensureInit() try # Сначала получаем документ чтобы получить _rev doc = await @getDocument(docId) if @useLocalDB and @localDb result = await @localDb.remove(doc) log '🗑️ Документ удален из локальной базы: ' + docId return result else result = await @remoteDb.remove(doc) log '🗑️ Документ удален из удаленной базы: ' + docId return result catch error log '❌ Ошибка удаления документа: ' + error throw error find: (selector, options = {}) -> @ensureInit() try if @useLocalDB and @localDb result = await @localDb.find(selector, options) log '🔍 Выполнен поиск в локальной базе: ' + JSON.stringify(selector) return result else result = await @remoteDb.find(selector, options) log '🔍 Выполнен поиск в удаленной базе: ' + JSON.stringify(selector) return result catch error log '❌ Ошибка поиска: ' + error throw error # Получение статистики базы данных getDatabaseInfo: -> @ensureInit() try if @useLocalDB and @localDb info = await @localDb.info() log '📊 Информация о локальной базе получена' return info else info = await @remoteDb.info() log '📊 Информация об удаленной базе получена' return info catch error log '❌ Ошибка получения информации о базе: ' + error throw error # Очистка локальной базы (только для разработки) clearLocalDatabase: -> return unless @useLocalDB and @localDb try await @localDb.destroy() log '🗑️ Локальная база данных очищена' # Пересоздаем локальную базу @localDb = new PouchDB(@localDbName or 'braer_color_cache') log '✅ Локальная база данных пересоздана' return true catch error log '❌ Ошибка очистки локальной базы: ' + error throw error # Проверка подключения к удаленной базе checkRemoteConnection: -> @ensureInit() try startTime = Date.now() info = await @remoteDb.info() endTime = Date.now() responseTime = endTime - startTime log '🌐 Проверка подключения к удаленной базе: ' + responseTime + 'мс' return { connected: true responseTime: responseTime info: info } catch error log '❌ Нет подключения к удаленной базе: ' + error return { connected: false error: error.message } # Получение статуса синхронизации getSyncStatus: -> return { initialized: @initialized syncStatus: @syncStatus useLocalDB: @useLocalDB localDb: if @localDb then 'connected' else 'disconnected' remoteDb: if @remoteDb then 'connected' else 'disconnected' } # Включение/выключение локальной базы setLocalDBEnabled: (enabled) -> if enabled != @useLocalDB @useLocalDB = enabled if enabled and not @localDb @localDb = new PouchDB(@localDbName or 'braer_color_cache') log '✅ Локальная PouchDB включена и инициализирована' # Запускаем синхронизацию if @remoteDb @setupSync() else if not enabled and @localDb if @syncHandler @syncHandler.cancel() await @localDb.close() @localDb = null log '⚠️ Локальная PouchDB отключена' return true ensureInit: -> unless @initialized throw new Error('PouchDB сервис не инициализирован. Вызовите init() сначала.') destroy: -> if @syncHandler @syncHandler.cancel() if @localDb await @localDb.close() @initialized = false log '🔚 PouchDB сервис остановлен' module.exports = new PouchDBService({ localDbName: 'braer_color_cache', remoteDbUrl: 'https://oleg:631074@couchdb.favt.ru.net/braer_color_shop', appVersion: '1.0.0' })