pouch.coffee 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. # app/utils/pouch.coffee
  2. class PouchDBService
  3. constructor: (options = {}) ->
  4. {@localDbName, @remoteDbUrl, @appVersion} = options
  5. @localDb = null
  6. @remoteDb = null
  7. @syncHandler = null
  8. @initialized = false
  9. @syncStatus = 'disconnected'
  10. @useLocalDB = false # Отключаем локальную БД временно
  11. init: ->
  12. return Promise.resolve() if @initialized
  13. try
  14. log '🔄 Инициализация PouchDB сервиса'
  15. # Отключаем локальную базу в браузере
  16. if @useLocalDB
  17. @localDb = new PouchDB(@localDbName or 'braer_color_cache')
  18. log '✅ Локальная PouchDB инициализирована'
  19. else
  20. log '⚠️ Локальная PouchDB отключена'
  21. # Инициализация удаленной базы
  22. @remoteDb = new PouchDB(@remoteDbUrl, {
  23. skip_setup: true # Не создавать автоматически
  24. fetch: (url, opts) =>
  25. opts.credentials = 'include'
  26. opts.headers ?= {}
  27. # Добавляем базовую аутентификацию если нужно
  28. PouchDB.fetch(url, opts)
  29. })
  30. # Проверяем и создаем базу если нужно
  31. await @ensureDatabaseExists()
  32. log '✅ Удаленная CouchDB подключена'
  33. # Создаем дизайн-документы
  34. await @ensureDesignDocuments()
  35. log '✅ Дизайн-документы проверены/созданы'
  36. # Настройка синхронизации (если локальная БД включена)
  37. if @useLocalDB
  38. @setupSync()
  39. @initialized = true
  40. log '🎉 PouchDB сервис полностью инициализирован'
  41. return Promise.resolve()
  42. catch error
  43. log '❌ Критическая ошибка инициализации PouchDB: ' + error
  44. return Promise.reject(error)
  45. ensureDatabaseExists: ->
  46. try
  47. # Пробуем получить информацию о базе
  48. info = await @remoteDb.info()
  49. log '📊 Информация о базе: ' + JSON.stringify(info)
  50. return true
  51. catch error
  52. if error.status == 404
  53. log '📦 База данных не найдена, создаем новую...'
  54. # Создаем базу через отдельный запрос
  55. await @createDatabase()
  56. return true
  57. else
  58. log '❌ Ошибка подключения к базе: ' + error
  59. throw error
  60. createDatabase: ->
  61. try
  62. # Создаем базу через PUT запрос
  63. response = await fetch(@remoteDbUrl, {
  64. method: 'PUT'
  65. headers: {
  66. 'Content-Type': 'application/json'
  67. }
  68. })
  69. if response.ok
  70. log '✅ База данных успешно создана'
  71. # Переинициализируем подключение
  72. @remoteDb = new PouchDB(@remoteDbUrl, {
  73. skip_setup: false
  74. })
  75. else
  76. throw new Error('Не удалось создать базу: ' + response.status)
  77. catch error
  78. log '❌ Ошибка создания базы данных: ' + error
  79. throw error
  80. ensureDesignDocuments: ->
  81. designDocs = require 'app/design/site'
  82. promises = []
  83. for name, doc of designDocs.designDocs
  84. promises.push(@ensureDesignDocument(doc))
  85. await Promise.all(promises)
  86. log '✅ Все дизайн-документы проверены'
  87. ensureDesignDocument: (designDoc) ->
  88. try
  89. # Пробуем получить существующий дизайн-документ
  90. existingDoc = await @remoteDb.get(designDoc._id)
  91. # Сравниваем версии или хеши для определения необходимости обновления
  92. if @needsDesignDocUpdate(existingDoc, designDoc)
  93. designDoc._rev = existingDoc._rev
  94. await @remoteDb.put(designDoc)
  95. log '✅ Дизайн-документ обновлен: ' + designDoc._id
  96. else
  97. log '⚠️ Дизайн-документ уже актуален: ' + designDoc._id
  98. catch error
  99. if error.status == 404
  100. # Дизайн-документ не существует, создаем новый
  101. await @remoteDb.put(designDoc)
  102. log '✅ Дизайн-документ создан: ' + designDoc._id
  103. else
  104. log '❌ Ошибка работы с дизайн-документом ' + designDoc._id + ': ' + error
  105. throw error
  106. needsDesignDocUpdate: (existingDoc, newDoc) ->
  107. # Простая проверка: сравниваем хеш views
  108. existingViewsHash = JSON.stringify(existingDoc.views)
  109. newViewsHash = JSON.stringify(newDoc.views)
  110. return existingViewsHash != newViewsHash
  111. setupSync: ->
  112. return unless @useLocalDB and @localDb and @remoteDb
  113. @syncHandler = PouchDB.sync(@localDb, @remoteDb, {
  114. live: true,
  115. retry: true,
  116. batch_size: 50,
  117. batches_limit: 10,
  118. filter: (doc) => @shouldSyncDocument(doc)
  119. })
  120. .on 'change', (info) =>
  121. log '📡 Синхронизация: данные изменены'
  122. @syncStatus = 'syncing'
  123. .on 'paused', (err) =>
  124. log '⏸️ Синхронизация приостановлена'
  125. @syncStatus = 'paused'
  126. .on 'active', =>
  127. log '🔄 Синхронизация активна'
  128. @syncStatus = 'active'
  129. .on 'denied', (err) =>
  130. log '🚫 Доступ запрещен: ' + err
  131. @syncStatus = 'denied'
  132. .on 'complete', (info) =>
  133. log '✅ Синхронизация завершена'
  134. @syncStatus = 'complete'
  135. .on 'error', (err) =>
  136. log '❌ Ошибка синхронизации: ' + err
  137. @syncStatus = 'error'
  138. @handleSyncError(err)
  139. shouldSyncDocument: (doc) ->
  140. return true if doc.type in ['product', 'category', 'domain_settings', 'hero_slide']
  141. return true if doc.type in ['blog_article', 'route', 'user_settings']
  142. if doc.type in ['order', 'user_data', 'cart']
  143. return doc.userId == @getCurrentUserId()
  144. return false
  145. getCurrentUserId: ->
  146. userData = localStorage.getItem('user')
  147. if userData
  148. try
  149. user = JSON.parse(userData)
  150. return user.id
  151. catch
  152. return 'anonymous'
  153. return 'anonymous'
  154. handleSyncError: (error) ->
  155. log '🔄 Обработка ошибки синхронизации: ' + error
  156. if error.status in [408, 429, 500, 502, 503, 504]
  157. log '⏳ Временная ошибка, повтор через 5 секунд...'
  158. setTimeout (=>
  159. @setupSync()
  160. ), 5000
  161. else if error.status == 401
  162. log '🔐 Ошибка аутентификации, требуется перелогин'
  163. document.dispatchEvent(new CustomEvent('auth-required'))
  164. # Основные методы работы с данными
  165. getDocument: (docId) ->
  166. @ensureInit()
  167. try
  168. if @useLocalDB and @localDb
  169. doc = await @localDb.get(docId)
  170. log '📄 Документ получен из локальной базы: ' + docId
  171. return doc
  172. else
  173. doc = await @remoteDb.get(docId)
  174. log '📄 Документ получен из удаленной базы: ' + docId
  175. return doc
  176. catch error
  177. log '❌ Ошибка получения документа: ' + error
  178. throw error
  179. saveDocument: (doc) ->
  180. @ensureInit()
  181. try
  182. if @useLocalDB and @localDb
  183. result = await @localDb.put(doc)
  184. log '💾 Документ сохранен локально: ' + doc._id
  185. return result
  186. else
  187. result = await @remoteDb.put(doc)
  188. log '💾 Документ сохранен в удаленной базе: ' + doc._id
  189. return result
  190. catch error
  191. log '❌ Ошибка сохранения документа: ' + error
  192. throw error
  193. bulkDocs: (docs) ->
  194. @ensureInit()
  195. try
  196. if @useLocalDB and @localDb
  197. result = await @localDb.bulkDocs(docs)
  198. log '📦 Пакетное сохранение документов в локальную базу: ' + docs.length
  199. return result
  200. else
  201. result = await @remoteDb.bulkDocs(docs)
  202. log '📦 Пакетное сохранение документов в удаленную базу: ' + docs.length
  203. return result
  204. catch error
  205. log '❌ Ошибка пакетного сохранения: ' + error
  206. throw error
  207. queryView: (designDoc, viewName, options = {}) ->
  208. @ensureInit()
  209. try
  210. if @useLocalDB and @localDb
  211. result = await @localDb.query(designDoc + '/' + viewName, options)
  212. log '🔍 Выполнен запрос к локальной view: ' + designDoc + '/' + viewName
  213. return result
  214. else
  215. result = await @remoteDb.query(designDoc + '/' + viewName, options)
  216. log '🔍 Выполнен запрос к удаленной view: ' + designDoc + '/' + viewName
  217. return result
  218. catch error
  219. log '❌ Ошибка запроса к view: ' + error
  220. throw error
  221. allDocs: (options = {}) ->
  222. @ensureInit()
  223. try
  224. if @useLocalDB and @localDb
  225. return await @localDb.allDocs(options)
  226. else
  227. return await @remoteDb.allDocs(options)
  228. catch error
  229. log '❌ Ошибка allDocs: ' + error
  230. throw error
  231. putAttachment: (docId, attachmentId, attachment, type) ->
  232. @ensureInit()
  233. try
  234. if @useLocalDB and @localDb
  235. result = await @localDb.putAttachment(docId, attachmentId, attachment, type)
  236. log '📎 Вложение добавлено в локальную базу: ' + docId + '/' + attachmentId
  237. return result
  238. else
  239. result = await @remoteDb.putAttachment(docId, attachmentId, attachment, type)
  240. log '📎 Вложение добавлено в удаленную базу: ' + docId + '/' + attachmentId
  241. return result
  242. catch error
  243. log '❌ Ошибка добавления вложения: ' + error
  244. throw error
  245. getAttachment: (docId, attachmentId) ->
  246. @ensureInit()
  247. try
  248. if @useLocalDB and @localDb
  249. attachment = await @localDb.getAttachment(docId, attachmentId)
  250. log '📎 Вложение получено из локальной базы: ' + docId + '/' + attachmentId
  251. return attachment
  252. else
  253. attachment = await @remoteDb.getAttachment(docId, attachmentId)
  254. log '📎 Вложение получено из удаленной базы: ' + docId + '/' + attachmentId
  255. return attachment
  256. catch error
  257. log '❌ Ошибка получения вложения: ' + error
  258. throw error
  259. removeDocument: (docId) ->
  260. @ensureInit()
  261. try
  262. # Сначала получаем документ чтобы получить _rev
  263. doc = await @getDocument(docId)
  264. if @useLocalDB and @localDb
  265. result = await @localDb.remove(doc)
  266. log '🗑️ Документ удален из локальной базы: ' + docId
  267. return result
  268. else
  269. result = await @remoteDb.remove(doc)
  270. log '🗑️ Документ удален из удаленной базы: ' + docId
  271. return result
  272. catch error
  273. log '❌ Ошибка удаления документа: ' + error
  274. throw error
  275. find: (selector, options = {}) ->
  276. @ensureInit()
  277. try
  278. if @useLocalDB and @localDb
  279. result = await @localDb.find(selector, options)
  280. log '🔍 Выполнен поиск в локальной базе: ' + JSON.stringify(selector)
  281. return result
  282. else
  283. result = await @remoteDb.find(selector, options)
  284. log '🔍 Выполнен поиск в удаленной базе: ' + JSON.stringify(selector)
  285. return result
  286. catch error
  287. log '❌ Ошибка поиска: ' + error
  288. throw error
  289. # Получение статистики базы данных
  290. getDatabaseInfo: ->
  291. @ensureInit()
  292. try
  293. if @useLocalDB and @localDb
  294. info = await @localDb.info()
  295. log '📊 Информация о локальной базе получена'
  296. return info
  297. else
  298. info = await @remoteDb.info()
  299. log '📊 Информация об удаленной базе получена'
  300. return info
  301. catch error
  302. log '❌ Ошибка получения информации о базе: ' + error
  303. throw error
  304. # Очистка локальной базы (только для разработки)
  305. clearLocalDatabase: ->
  306. return unless @useLocalDB and @localDb
  307. try
  308. await @localDb.destroy()
  309. log '🗑️ Локальная база данных очищена'
  310. # Пересоздаем локальную базу
  311. @localDb = new PouchDB(@localDbName or 'braer_color_cache')
  312. log '✅ Локальная база данных пересоздана'
  313. return true
  314. catch error
  315. log '❌ Ошибка очистки локальной базы: ' + error
  316. throw error
  317. # Проверка подключения к удаленной базе
  318. checkRemoteConnection: ->
  319. @ensureInit()
  320. try
  321. startTime = Date.now()
  322. info = await @remoteDb.info()
  323. endTime = Date.now()
  324. responseTime = endTime - startTime
  325. log '🌐 Проверка подключения к удаленной базе: ' + responseTime + 'мс'
  326. return {
  327. connected: true
  328. responseTime: responseTime
  329. info: info
  330. }
  331. catch error
  332. log '❌ Нет подключения к удаленной базе: ' + error
  333. return {
  334. connected: false
  335. error: error.message
  336. }
  337. # Получение статуса синхронизации
  338. getSyncStatus: ->
  339. return {
  340. initialized: @initialized
  341. syncStatus: @syncStatus
  342. useLocalDB: @useLocalDB
  343. localDb: if @localDb then 'connected' else 'disconnected'
  344. remoteDb: if @remoteDb then 'connected' else 'disconnected'
  345. }
  346. # Включение/выключение локальной базы
  347. setLocalDBEnabled: (enabled) ->
  348. if enabled != @useLocalDB
  349. @useLocalDB = enabled
  350. if enabled and not @localDb
  351. @localDb = new PouchDB(@localDbName or 'braer_color_cache')
  352. log '✅ Локальная PouchDB включена и инициализирована'
  353. # Запускаем синхронизацию
  354. if @remoteDb
  355. @setupSync()
  356. else if not enabled and @localDb
  357. if @syncHandler
  358. @syncHandler.cancel()
  359. await @localDb.close()
  360. @localDb = null
  361. log '⚠️ Локальная PouchDB отключена'
  362. return true
  363. ensureInit: ->
  364. unless @initialized
  365. throw new Error('PouchDB сервис не инициализирован. Вызовите init() сначала.')
  366. destroy: ->
  367. if @syncHandler
  368. @syncHandler.cancel()
  369. if @localDb
  370. await @localDb.close()
  371. @initialized = false
  372. log '🔚 PouchDB сервис остановлен'
  373. module.exports = new PouchDBService({
  374. localDbName: 'braer_color_cache',
  375. remoteDbUrl: 'https://oleg:631074@couchdb.favt.ru.net/braer_color_shop',
  376. appVersion: '1.0.0'
  377. })