design-documents.coffee 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. # Design документы для CouchDB с поддержкой мультиязычных массивов и наследования
  2. # Версия 6.0 - унифицированная структура данных
  3. module.exports =
  4. # Design документ для универсального поиска по мультиязычному контенту
  5. universal_multilingual_search:
  6. version: "6.0"
  7. views:
  8. # Универсальный поиск по всем типам контента с поддержкой массивов
  9. content_search:
  10. map: ((doc) ->
  11. # Пропускаем системные документы
  12. if doc._id.startsWith('_design/') or doc.type in ['domain_settings', 'user', 'order', 'setting', 'audit_log']
  13. return
  14. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  15. languages = doc.language or ['ru']
  16. # Базовые поля для поиска
  17. searchFields = {}
  18. switch doc.type
  19. when 'blog_post', 'event', 'product', 'slide'
  20. if doc.status is 'published'
  21. searchFields =
  22. titles: doc.title or []
  23. contents: doc.content or []
  24. excerpts: doc.excerpt or []
  25. authors: doc.author or []
  26. tags: doc.tags or []
  27. type: doc.type
  28. languages: languages
  29. when 'category'
  30. searchFields =
  31. names: doc.name or []
  32. descriptions: doc.description or []
  33. type: 'category'
  34. languages: languages
  35. # Индексируем для каждого домена и языка
  36. if searchFields.titles
  37. for domain in domains
  38. for language, index in languages
  39. # Текст для поиска на конкретном языке
  40. searchText = (
  41. (searchFields.titles[index] or '') + " " +
  42. (searchFields.contents[index] or '') + " " +
  43. (searchFields.excerpts[index] or '') + " " +
  44. (searchFields.authors[index] or '') + " " +
  45. (searchFields.names?[index] or '') + " " +
  46. (searchFields.descriptions?[index] or '')
  47. ).toLowerCase()
  48. # Добавляем теги
  49. if searchFields.tags[index]
  50. searchText += " " + searchFields.tags[index].join(" ")
  51. words = searchText.split(/\W+/).filter (word) -> word.length > 2
  52. for word in words
  53. emit([domain, language, word], {
  54. _id: doc._id
  55. type: searchFields.type
  56. title: searchFields.titles[index]
  57. excerpt: searchFields.excerpts[index]
  58. language: language
  59. domain: domain
  60. created_at: doc.created_at
  61. })
  62. ).toString()
  63. # Поиск по конкретному типу контента
  64. by_content_type:
  65. map: ((doc) ->
  66. if doc.type in ['blog_post', 'event', 'product', 'slide'] and doc.status is 'published'
  67. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  68. languages = doc.language or ['ru']
  69. for domain in domains
  70. for language, index in languages
  71. emit([domain, doc.type, language, doc.created_at], {
  72. _id: doc._id
  73. title: doc.title?[index]
  74. excerpt: doc.excerpt?[index]
  75. image: doc.image?[index]
  76. author: doc.author?[index]
  77. language: language
  78. featured: doc.featured or false
  79. })
  80. ).toString()
  81. # Design документ для работы с мультиязычными блог постами и наследниками
  82. multilingual_content:
  83. version: "6.0"
  84. views:
  85. # Все опубликованные документы контента по доменам и языкам
  86. published_by_domain_language:
  87. map: ((doc) ->
  88. if doc.type in ['blog_post', 'event', 'product', 'slide'] and doc.status is 'published'
  89. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  90. languages = doc.language or ['ru']
  91. for domain in domains
  92. for language, index in languages
  93. contentData = {
  94. _id: doc._id
  95. type: doc.type
  96. title: doc.title?[index]
  97. excerpt: doc.excerpt?[index]
  98. image: doc.image?[index]
  99. author: doc.author?[index]
  100. created_at: doc.created_at
  101. published_at: doc.published_at
  102. featured: doc.featured or false
  103. views: doc.views or 0
  104. language: language
  105. domain: domain
  106. }
  107. # Добавляем специфичные поля для наследников
  108. switch doc.type
  109. when 'event'
  110. contentData.event_date = doc.event_data?.event_date
  111. contentData.location = doc.event_data?.location?[index]
  112. contentData.price = doc.event_data?.price?[index]
  113. contentData.status = doc.event_data?.status
  114. when 'product'
  115. contentData.price = doc.product_data?.price?[index]
  116. contentData.status = doc.product_data?.status
  117. contentData.inventory = doc.product_data?.inventory
  118. when 'slide'
  119. contentData.order = doc.slide_data?.order
  120. contentData.active = doc.slide_data?.active
  121. emit([domain, language, doc.created_at], contentData)
  122. ).toString()
  123. # Избранный контент с приоритетом
  124. featured_content:
  125. map: ((doc) ->
  126. if doc.type in ['blog_post', 'event', 'product', 'slide'] and doc.status is 'published' and doc.featured is true
  127. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  128. languages = doc.language or ['ru']
  129. for domain in domains
  130. for language, index in languages
  131. emit([domain, language, doc.featured, doc.created_at], {
  132. _id: doc._id
  133. type: doc.type
  134. title: doc.title?[index]
  135. excerpt: doc.excerpt?[index]
  136. image: doc.image?[index]
  137. language: language
  138. })
  139. ).toString()
  140. # Контент по категориям с поддержкой иерархии
  141. by_category_path:
  142. map: ((doc) ->
  143. if doc.type in ['blog_post', 'event', 'product', 'slide'] and doc.status is 'published' and doc.category_path
  144. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  145. languages = doc.language or ['ru']
  146. for domain in domains
  147. for language, index in languages
  148. # Эмитим для каждой категории в пути
  149. for category_id in doc.category_path
  150. emit([domain, language, category_id, doc.created_at], {
  151. _id: doc._id
  152. type: doc.type
  153. title: doc.title?[index]
  154. category_id: doc.category_id
  155. language: language
  156. })
  157. ).toString()
  158. # Design документ для событий (events) с мультиязычными данными
  159. events_multilingual:
  160. version: "6.0"
  161. views:
  162. # События по дате с мультиязычной информацией
  163. by_date_multilingual:
  164. map: ((doc) ->
  165. if doc.type is 'event' and doc.status is 'published'
  166. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  167. languages = doc.language or ['ru']
  168. event_date = doc.event_data?.event_date
  169. if event_date
  170. for domain in domains
  171. for language, index in languages
  172. emit([domain, language, event_date], {
  173. _id: doc._id
  174. title: doc.title?[index]
  175. excerpt: doc.excerpt?[index]
  176. location: doc.event_data?.location?[index]
  177. price: doc.event_data?.price?[index]
  178. available_tickets: doc.event_data?.available_tickets
  179. status: doc.event_data?.status
  180. image: doc.image?[index]
  181. language: language
  182. })
  183. ).toString()
  184. # Предстоящие события
  185. upcoming_events:
  186. map: ((doc) ->
  187. if doc.type is 'event' and doc.event_data?.status is 'upcoming'
  188. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  189. languages = doc.language or ['ru']
  190. event_date = doc.event_data?.event_date
  191. if event_date
  192. for domain in domains
  193. for language, index in languages
  194. emit([domain, language, event_date], {
  195. _id: doc._id
  196. title: doc.title?[index]
  197. location: doc.event_data?.location?[index]
  198. price: doc.event_data?.price?[index]
  199. available_tickets: doc.event_data?.available_tickets
  200. image: doc.image?[index]
  201. language: language
  202. })
  203. ).toString()
  204. # События по местоположению
  205. by_location_multilingual:
  206. map: ((doc) ->
  207. if doc.type is 'event' and doc.status is 'published'
  208. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  209. languages = doc.language or ['ru']
  210. locations = doc.event_data?.location or []
  211. for domain in domains
  212. for language, index in languages
  213. location = locations[index]
  214. if location
  215. emit([domain, language, location, doc.event_data?.event_date], {
  216. _id: doc._id
  217. title: doc.title?[index]
  218. event_date: doc.event_data?.event_date
  219. price: doc.event_data?.price?[index]
  220. language: language
  221. })
  222. ).toString()
  223. # Design документ для товаров (products) с мультиязычными данными
  224. products_multilingual:
  225. version: "6.0"
  226. views:
  227. # Товары по статусу и доступности
  228. by_status_multilingual:
  229. map: ((doc) ->
  230. if doc.type is 'product' and doc.status is 'published'
  231. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  232. languages = doc.language or ['ru']
  233. product_status = doc.product_data?.status
  234. for domain in domains
  235. for language, index in languages
  236. emit([domain, language, product_status, doc.created_at], {
  237. _id: doc._id
  238. title: doc.title?[index]
  239. price: doc.product_data?.price?[index]
  240. compare_price: doc.product_data?.compare_price?[index]
  241. inventory: doc.product_data?.inventory
  242. sku: doc.product_data?.sku
  243. image: doc.image?[index]
  244. language: language
  245. })
  246. ).toString()
  247. # Товары по цене
  248. by_price_range:
  249. map: ((doc) ->
  250. if doc.type is 'product' and doc.status is 'published' and doc.product_data?.status is 'available'
  251. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  252. languages = doc.language or ['ru']
  253. prices = doc.product_data?.price or []
  254. for domain in domains
  255. for language, index in languages
  256. price = prices[index]
  257. if price
  258. # Группируем по ценовым диапазонам
  259. price_range = Math.floor(price / 100) * 100
  260. emit([domain, language, price_range, doc.created_at], {
  261. _id: doc._id
  262. title: doc.title?[index]
  263. price: price
  264. inventory: doc.product_data?.inventory
  265. image: doc.image?[index]
  266. language: language
  267. })
  268. ).toString()
  269. # Товары по тегам
  270. by_tags_multilingual:
  271. map: ((doc) ->
  272. if doc.type is 'product' and doc.status is 'published'
  273. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  274. languages = doc.language or ['ru']
  275. tagsArrays = doc.tags or []
  276. for domain in domains
  277. for language, index in languages
  278. tags = tagsArrays[index] or []
  279. for tag in tags
  280. emit([domain, language, tag, doc.created_at], {
  281. _id: doc._id
  282. title: doc.title?[index]
  283. price: doc.product_data?.price?[index]
  284. image: doc.image?[index]
  285. language: language
  286. })
  287. ).toString()
  288. # Design документ для слайдеров (slides) с мультиязычными данными
  289. slides_multilingual:
  290. version: "6.0"
  291. views:
  292. # Активные слайды по порядку
  293. active_ordered_multilingual:
  294. map: ((doc) ->
  295. if doc.type is 'slide' and doc.slide_data?.active is true
  296. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  297. languages = doc.language or ['ru']
  298. order = doc.slide_data?.order or 0
  299. for domain in domains
  300. for language, index in languages
  301. emit([domain, language, order], {
  302. _id: doc._id
  303. title: doc.title?[index]
  304. content: doc.content?[index]
  305. image: doc.image?[index]
  306. button_text: doc.slide_data?.button_text?[index]
  307. button_link: doc.slide_data?.button_link?[index]
  308. language: language
  309. })
  310. ).toString()
  311. # Слайды по датам активности
  312. by_active_dates:
  313. map: ((doc) ->
  314. if doc.type is 'slide' and doc.slide_data?.active is true
  315. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  316. start_date = doc.slide_data?.start_date
  317. end_date = doc.slide_data?.end_date
  318. if start_date and end_date
  319. for domain in domains
  320. emit([domain, start_date, end_date], {
  321. _id: doc._id
  322. title: doc.title?[0] # Берем первый язык для ключа
  323. order: doc.slide_data?.order
  324. })
  325. ).toString()
  326. # Design документ для иерархических категорий с мультиязычностью
  327. categories_hierarchical_multilingual:
  328. version: "6.0"
  329. views:
  330. # Категории по уровню иерархии
  331. by_level_multilingual:
  332. map: ((doc) ->
  333. if doc.type is 'category'
  334. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  335. languages = doc.language or ['ru']
  336. level = doc.level or 0
  337. for domain in domains
  338. for language, index in languages
  339. emit([domain, language, level, doc.order], {
  340. _id: doc._id
  341. name: doc.name?[index]
  342. slug: doc.slug?[index]
  343. description: doc.description?[index]
  344. parent_id: doc.parent_id
  345. level: level
  346. order: doc.order
  347. active: doc.active
  348. featured: doc.featured
  349. show_in_menu: doc.show_in_menu
  350. language: language
  351. })
  352. ).toString()
  353. # Категории по родителю для построения дерева
  354. by_parent_multilingual:
  355. map: ((doc) ->
  356. if doc.type is 'category'
  357. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  358. languages = doc.language or ['ru']
  359. parent_id = doc.parent_id or 'root'
  360. for domain in domains
  361. for language, index in languages
  362. emit([domain, language, parent_id, doc.order], {
  363. _id: doc._id
  364. name: doc.name?[index]
  365. slug: doc.slug?[index]
  366. level: doc.level
  367. children_count: doc.children_count
  368. order: doc.order
  369. active: doc.active
  370. language: language
  371. })
  372. ).toString()
  373. # Корневые категории
  374. root_categories_multilingual:
  375. map: ((doc) ->
  376. if doc.type is 'category' and (not doc.parent_id or doc.parent_id is null)
  377. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  378. languages = doc.language or ['ru']
  379. for domain in domains
  380. for language, index in languages
  381. emit([domain, language, doc.order], {
  382. _id: doc._id
  383. name: doc.name?[index]
  384. slug: doc.slug?[index]
  385. level: 0
  386. children_count: doc.children_count
  387. order: doc.order
  388. active: doc.active
  389. featured: doc.featured
  390. language: language
  391. })
  392. ).toString()
  393. # Design документ для статистики и аналитики мультиязычного контента
  394. multilingual_statistics:
  395. version: "6.0"
  396. views:
  397. # Статистика по типам контента и языкам
  398. content_stats_by_language:
  399. map: ((doc) ->
  400. if doc.type in ['blog_post', 'event', 'product', 'slide'] and doc.status is 'published'
  401. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  402. languages = doc.language or ['ru']
  403. for domain in domains
  404. for language in languages
  405. # Статистика по типам
  406. emit([domain, 'type_count', doc.type, language], 1)
  407. # Статистика по датам
  408. if doc.created_at
  409. date = doc.created_at.split('T')[0]
  410. emit([domain, 'creation_date', date, language], 1)
  411. # Статистика просмотров
  412. if doc.views
  413. emit([domain, 'views', doc.type, language], doc.views)
  414. ).toString()
  415. reduce: ((keys, values) ->
  416. sum values
  417. ).toString()
  418. # Статистика популярности контента
  419. content_popularity_multilingual:
  420. map: ((doc) ->
  421. if doc.type is 'blog_post'
  422. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  423. languages = doc.language or ['ru']
  424. views = doc.views or 0
  425. likes = doc.likes or 0
  426. shares = doc.shares or 0
  427. popularity = views + (likes * 2) + (shares * 3)
  428. for domain in domains
  429. for language in languages
  430. emit([domain, language, popularity], {
  431. _id: doc._id
  432. type: doc.type
  433. title: doc.title?[0] # Первый язык для ключа
  434. views: views
  435. likes: likes
  436. shares: shares
  437. popularity: popularity
  438. })
  439. else if doc.type is 'event'
  440. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  441. languages = doc.language or ['ru']
  442. tickets_sold = (doc.event_data?.total_tickets or 0) - (doc.event_data?.available_tickets or 0)
  443. for domain in domains
  444. for language in languages
  445. emit([domain, language, tickets_sold], {
  446. _id: doc._id
  447. type: doc.type
  448. title: doc.title?[0]
  449. tickets_sold: tickets_sold
  450. total_tickets: doc.event_data?.total_tickets
  451. })
  452. ).toString()
  453. # Design документ для работы с заказами
  454. orders_management:
  455. version: "6.0"
  456. views:
  457. # Заказы по статусу и домену
  458. by_status_and_domain:
  459. map: ((doc) ->
  460. if doc.type is 'order'
  461. domain = doc.domain or 'default'
  462. languages = doc.language or ['ru']
  463. for language in languages
  464. emit([domain, language, doc.status, doc.created_at], {
  465. _id: doc._id
  466. total: doc.total
  467. currency: doc.currency
  468. customer_name: doc.customer_info?.name?[0] # Первый язык
  469. created_at: doc.created_at
  470. items_count: doc.items?.length or 0
  471. })
  472. ).toString()
  473. # Статистика продаж по доменам и языкам
  474. sales_statistics_multilingual:
  475. map: ((doc) ->
  476. if doc.type is 'order' and doc.status is 'completed'
  477. domain = doc.domain or 'default'
  478. languages = doc.language or ['ru']
  479. date = doc.created_at.split('T')[0]
  480. for language in languages
  481. # Общая сумма за день
  482. emit([domain, language, 'daily_sales', date], doc.total)
  483. # Количество заказов за день
  484. emit([domain, language, 'daily_orders', date], 1)
  485. # Статистика по товарам
  486. if doc.items
  487. for item in doc.items
  488. emit([domain, language, 'product_sales', item.product_id], item.quantity)
  489. emit([domain, language, 'product_revenue', item.product_id], item.total)
  490. ).toString()
  491. reduce: ((keys, values) ->
  492. sum values
  493. ).toString()
  494. # Design документ для пользователей и управления доступом
  495. users_management:
  496. version: "6.0"
  497. views:
  498. # Пользователи по email и роли
  499. by_email_and_role:
  500. map: ((doc) ->
  501. if doc.type is 'user'
  502. emit([doc.email, doc.role], {
  503. _id: doc._id
  504. name: doc.name?[0] # Первый язык
  505. active: doc.active
  506. last_login: doc.last_login
  507. domains_access: doc.domains_access
  508. })
  509. ).toString()
  510. # Активные пользователи по доменам
  511. active_users_by_domain:
  512. map: ((doc) ->
  513. if doc.type is 'user' and doc.active is true
  514. domains = doc.domains_access or []
  515. for domain in domains
  516. emit([domain, doc.role, doc.email], {
  517. _id: doc._id
  518. name: doc.name?[0]
  519. email: doc.email
  520. last_login: doc.last_login
  521. })
  522. ).toString()
  523. # Design документ для системы уведомлений с мультиязычностью
  524. multilingual_notifications:
  525. version: "6.0"
  526. views:
  527. # Уведомления о предстоящих событиях
  528. event_reminders_multilingual:
  529. map: ((doc) ->
  530. if doc.type is 'event' and doc.event_data?.status is 'upcoming'
  531. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  532. languages = doc.language or ['ru']
  533. event_date = new Date(doc.event_data.event_date)
  534. now = new Date()
  535. # Уведомление за 7 дней до события
  536. sevenDaysBefore = new Date(event_date.getTime() - 7 * 24 * 60 * 60 * 1000)
  537. if now >= sevenDaysBefore and now < event_date
  538. for domain in domains
  539. for language, index in languages
  540. emit([domain, language, 'event_reminder_7d', doc.event_data.event_date], {
  541. _id: doc._id
  542. title: doc.title?[index]
  543. event_date: doc.event_data.event_date
  544. domain: domain
  545. language: language
  546. notification_type: 'event_reminder_7d'
  547. })
  548. # Уведомление за 1 день до события
  549. oneDayBefore = new Date(event_date.getTime() - 24 * 60 * 60 * 1000)
  550. if now >= oneDayBefore and now < event_date
  551. for domain in domains
  552. for language, index in languages
  553. emit([domain, language, 'event_reminder_1d', doc.event_data.event_date], {
  554. _id: doc._id
  555. title: doc.title?[index]
  556. event_date: doc.event_data.event_date
  557. domain: domain
  558. language: language
  559. notification_type: 'event_reminder_1d'
  560. })
  561. ).toString()
  562. # Уведомления о низком количестве товаров
  563. low_inventory_alerts:
  564. map: ((doc) ->
  565. if doc.type is 'product' and doc.product_data?.inventory < 10
  566. domains = if Array.isArray(doc.domain) then doc.domain else [doc.domain or 'default']
  567. languages = doc.language or ['ru']
  568. for domain in domains
  569. for language, index in languages
  570. emit([domain, language, 'low_inventory', doc.product_data.inventory], {
  571. _id: doc._id
  572. title: doc.title?[index]
  573. inventory: doc.product_data.inventory
  574. sku: doc.product_data.sku
  575. domain: domain
  576. language: language
  577. notification_type: 'low_inventory'
  578. })
  579. ).toString()