| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443 |
- # 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'
- })
|