|
@@ -0,0 +1,313 @@
|
|
|
|
|
+# app/services/MediaService.coffee
|
|
|
|
|
+{ DomainEntity } = require 'app/types/data'
|
|
|
|
|
+
|
|
|
|
|
+class MediaFile extends DomainEntity
|
|
|
|
|
+ constructor: ->
|
|
|
|
|
+ super()
|
|
|
|
|
+ @type = 'media_file'
|
|
|
|
|
+ @name = ''
|
|
|
|
|
+ @filename = ''
|
|
|
|
|
+ @size = 0
|
|
|
|
|
+ @mimeType = ''
|
|
|
|
|
+ @url = ''
|
|
|
|
|
+ @thumbnailUrl = ''
|
|
|
|
|
+ @description = ''
|
|
|
|
|
+ @tags = []
|
|
|
|
|
+ @attachedTo = [] # Массив объектов, к которым прикреплен файл
|
|
|
|
|
+
|
|
|
|
|
+class MediaService
|
|
|
|
|
+ constructor: ->
|
|
|
|
|
+ @pouchService = require 'app/utils/pouch'
|
|
|
|
|
+ @initialized = false
|
|
|
|
|
+
|
|
|
|
|
+ init: ->
|
|
|
|
|
+ return Promise.resolve() if @initialized
|
|
|
|
|
+
|
|
|
|
|
+ try
|
|
|
|
|
+ await @pouchService.init()
|
|
|
|
|
+ @initialized = true
|
|
|
|
|
+ log '✅ MediaService инициализирован'
|
|
|
|
|
+ return Promise.resolve()
|
|
|
|
|
+ catch error
|
|
|
|
|
+ log '❌ Ошибка инициализации MediaService: '+error.message
|
|
|
|
|
+ return Promise.reject(error)
|
|
|
|
|
+
|
|
|
|
|
+ getAllFiles: (options = {}) ->
|
|
|
|
|
+ await @ensureInit()
|
|
|
|
|
+
|
|
|
|
|
+ try
|
|
|
|
|
+ # Временная заглушка - возвращаем тестовые данные
|
|
|
|
|
+ # В реальном приложении здесь будет запрос к PouchDB
|
|
|
|
|
+ mockFiles = [
|
|
|
|
|
+ {
|
|
|
|
|
+ _id: 'media_file:1'
|
|
|
|
|
+ name: 'product-image-1.jpg'
|
|
|
|
|
+ filename: 'product-image-1.jpg'
|
|
|
|
|
+ size: 1024000
|
|
|
|
|
+ mimeType: 'image/jpeg'
|
|
|
|
|
+ type: 'media_file'
|
|
|
|
|
+ url: '/d/braer_color_shop/media_file:1/product-image-1.jpg'
|
|
|
|
|
+ thumbnailUrl: '/d/braer_color_shop/media_file:1/thumb-product-image-1.jpg'
|
|
|
|
|
+ createdAt: new Date().toISOString()
|
|
|
|
|
+ updatedAt: new Date().toISOString()
|
|
|
|
|
+ domains: [window.location.hostname]
|
|
|
|
|
+ active: true
|
|
|
|
|
+ }
|
|
|
|
|
+ {
|
|
|
|
|
+ _id: 'media_file:2'
|
|
|
|
|
+ name: 'category-banner.png'
|
|
|
|
|
+ filename: 'category-banner.png'
|
|
|
|
|
+ size: 2048000
|
|
|
|
|
+ mimeType: 'image/png'
|
|
|
|
|
+ type: 'media_file'
|
|
|
|
|
+ url: '/d/braer_color_shop/media_file:2/category-banner.png'
|
|
|
|
|
+ thumbnailUrl: '/d/braer_color_shop/media_file:2/thumb-category-banner.png'
|
|
|
|
|
+ createdAt: new Date(Date.now() - 86400000).toISOString()
|
|
|
|
|
+ updatedAt: new Date(Date.now() - 86400000).toISOString()
|
|
|
|
|
+ domains: [window.location.hostname]
|
|
|
|
|
+ active: true
|
|
|
|
|
+ }
|
|
|
|
|
+ {
|
|
|
|
|
+ _id: 'media_file:3'
|
|
|
|
|
+ name: 'product-specification.pdf'
|
|
|
|
|
+ filename: 'product-specification.pdf'
|
|
|
|
|
+ size: 512000
|
|
|
|
|
+ mimeType: 'application/pdf'
|
|
|
|
|
+ type: 'media_file'
|
|
|
|
|
+ url: '/d/braer_color_shop/media_file:3/product-specification.pdf'
|
|
|
|
|
+ createdAt: new Date(Date.now() - 172800000).toISOString()
|
|
|
|
|
+ updatedAt: new Date(Date.now() - 172800000).toISOString()
|
|
|
|
|
+ domains: [window.location.hostname]
|
|
|
|
|
+ active: true
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ # Фильтрация по опциям
|
|
|
|
|
+ files = mockFiles
|
|
|
|
|
+ if options.type
|
|
|
|
|
+ files = files.filter (file) -> file.mimeType.startsWith(options.type)
|
|
|
|
|
+
|
|
|
|
|
+ if options.search
|
|
|
|
|
+ query = options.search.toLowerCase()
|
|
|
|
|
+ files = files.filter (file) ->
|
|
|
|
|
+ file.name.toLowerCase().includes(query) or
|
|
|
|
|
+ file.filename.toLowerCase().includes(query)
|
|
|
|
|
+
|
|
|
|
|
+ log '✅ Медиа-файлы загружены: '+files.length
|
|
|
|
|
+ return files.map (file) -> new MediaFile(file)
|
|
|
|
|
+
|
|
|
|
|
+ catch error
|
|
|
|
|
+ log '❌ Ошибка загрузки медиа-файлов: '+error.message
|
|
|
|
|
+ throw error
|
|
|
|
|
+
|
|
|
|
|
+ uploadFiles: (files) ->
|
|
|
|
|
+ await @ensureInit()
|
|
|
|
|
+
|
|
|
|
|
+ try
|
|
|
|
|
+ log '🚀 Начало загрузки файлов: '+files.length
|
|
|
|
|
+
|
|
|
|
|
+ uploadedFiles = []
|
|
|
|
|
+
|
|
|
|
|
+ for file in files
|
|
|
|
|
+ # Создание документа медиа-файла
|
|
|
|
|
+ mediaFile = new MediaFile()
|
|
|
|
|
+ mediaFile._id = 'media_file:'+Date.now()+'_'+Math.random().toString(36).substr(2, 9)
|
|
|
|
|
+ mediaFile.name = file.name
|
|
|
|
|
+ mediaFile.filename = file.name
|
|
|
|
|
+ mediaFile.size = file.size
|
|
|
|
|
+ mediaFile.mimeType = file.type
|
|
|
|
|
+ mediaFile.domains = [window.location.hostname]
|
|
|
|
|
+
|
|
|
|
|
+ # Определение типа файла
|
|
|
|
|
+ if file.type.startsWith('image/')
|
|
|
|
|
+ mediaFile.type = 'image'
|
|
|
|
|
+ else if file.type.startsWith('application/')
|
|
|
|
|
+ mediaFile.type = 'document'
|
|
|
|
|
+ else
|
|
|
|
|
+ mediaFile.type = 'other'
|
|
|
|
|
+
|
|
|
|
|
+ # В реальном приложении здесь будет загрузка файла как attachment в PouchDB
|
|
|
|
|
+ # await @pouchService.putAttachment(mediaFile._id, file.name, file, file.type)
|
|
|
|
|
+
|
|
|
|
|
+ # Сохранение документа
|
|
|
|
|
+ # await @pouchService.saveDocument(mediaFile)
|
|
|
|
|
+
|
|
|
|
|
+ uploadedFiles.push(mediaFile)
|
|
|
|
|
+ log '✅ Файл загружен: '+file.name
|
|
|
|
|
+
|
|
|
|
|
+ log '🎉 Все файлы успешно загружены: '+uploadedFiles.length
|
|
|
|
|
+ return uploadedFiles
|
|
|
|
|
+
|
|
|
|
|
+ catch error
|
|
|
|
|
+ log '❌ Ошибка загрузки файлов: '+error.message
|
|
|
|
|
+ throw error
|
|
|
|
|
+
|
|
|
|
|
+ uploadFile: (file) ->
|
|
|
|
|
+ await @ensureInit()
|
|
|
|
|
+
|
|
|
|
|
+ try
|
|
|
|
|
+ log '📤 Загрузка файла: '+file.name
|
|
|
|
|
+
|
|
|
|
|
+ # Создание документа медиа-файла
|
|
|
|
|
+ mediaFile = new MediaFile()
|
|
|
|
|
+ mediaFile._id = 'media_file:'+Date.now()+'_'+Math.random().toString(36).substr(2, 9)
|
|
|
|
|
+ mediaFile.name = file.name
|
|
|
|
|
+ mediaFile.filename = file.name
|
|
|
|
|
+ mediaFile.size = file.size
|
|
|
|
|
+ mediaFile.mimeType = file.type
|
|
|
|
|
+ mediaFile.domains = [window.location.hostname]
|
|
|
|
|
+
|
|
|
|
|
+ # Определение типа файла
|
|
|
|
|
+ if file.type.startsWith('image/')
|
|
|
|
|
+ mediaFile.type = 'image'
|
|
|
|
|
+
|
|
|
|
|
+ # Создание thumbnail для изображений (в реальном приложении)
|
|
|
|
|
+ mediaFile.thumbnailUrl = '/d/braer_color_shop/'+mediaFile._id+'/thumb-'+file.name
|
|
|
|
|
+
|
|
|
|
|
+ else if file.type.startsWith('application/')
|
|
|
|
|
+ mediaFile.type = 'document'
|
|
|
|
|
+ else
|
|
|
|
|
+ mediaFile.type = 'other'
|
|
|
|
|
+
|
|
|
|
|
+ # В реальном приложении здесь будет:
|
|
|
|
|
+ # 1. Загрузка файла как attachment в PouchDB
|
|
|
|
|
+ # 2. Создание thumbnail для изображений
|
|
|
|
|
+ # 3. Сохранение документа медиа-файла
|
|
|
|
|
+
|
|
|
|
|
+ # await @pouchService.putAttachment(mediaFile._id, file.name, file, file.type)
|
|
|
|
|
+ # await @pouchService.saveDocument(mediaFile)
|
|
|
|
|
+
|
|
|
|
|
+ log '✅ Файл успешно загружен: '+file.name
|
|
|
|
|
+ return mediaFile
|
|
|
|
|
+
|
|
|
|
|
+ catch error
|
|
|
|
|
+ log '❌ Ошибка загрузки файла: '+error.message
|
|
|
|
|
+ throw error
|
|
|
|
|
+
|
|
|
|
|
+ deleteFile: (fileId) ->
|
|
|
|
|
+ await @ensureInit()
|
|
|
|
|
+
|
|
|
|
|
+ try
|
|
|
|
|
+ log '🗑️ Удаление файла: '+fileId
|
|
|
|
|
+
|
|
|
|
|
+ # В реальном приложении здесь будет:
|
|
|
|
|
+ # 1. Получение документа
|
|
|
|
|
+ # 2. Удаление attachments
|
|
|
|
|
+ # 3. Удаление документа
|
|
|
|
|
+
|
|
|
|
|
+ # const doc = await @pouchService.getDocument(fileId)
|
|
|
|
|
+ # await @pouchService.removeDocument(doc)
|
|
|
|
|
+
|
|
|
|
|
+ log '✅ Файл удален: '+fileId
|
|
|
|
|
+ return true
|
|
|
|
|
+
|
|
|
|
|
+ catch error
|
|
|
|
|
+ log '❌ Ошибка удаления файла: '+error.message
|
|
|
|
|
+ throw error
|
|
|
|
|
+
|
|
|
|
|
+ deleteFiles: (fileIds) ->
|
|
|
|
|
+ await @ensureInit()
|
|
|
|
|
+
|
|
|
|
|
+ try
|
|
|
|
|
+ log '🗑️ Пакетное удаление файлов: '+fileIds.length
|
|
|
|
|
+
|
|
|
|
|
+ results = []
|
|
|
|
|
+ for fileId in fileIds
|
|
|
|
|
+ try
|
|
|
|
|
+ # await @deleteFile(fileId)
|
|
|
|
|
+ results.push({ fileId: fileId, success: true })
|
|
|
|
|
+ catch error
|
|
|
|
|
+ results.push({ fileId: fileId, success: false, error: error.message })
|
|
|
|
|
+
|
|
|
|
|
+ successCount = results.filter((r) -> r.success).length
|
|
|
|
|
+ log '✅ Удалено файлов: '+successCount+' из '+fileIds.length
|
|
|
|
|
+ return results
|
|
|
|
|
+
|
|
|
|
|
+ catch error
|
|
|
|
|
+ log '❌ Ошибка пакетного удаления файлов: '+error.message
|
|
|
|
|
+ throw error
|
|
|
|
|
+
|
|
|
|
|
+ getFileUrl: (fileId, filename) ->
|
|
|
|
|
+ # Генерация URL для доступа к файлу в CouchDB
|
|
|
|
|
+ return '/d/braer_color_shop/'+fileId+'/'+filename
|
|
|
|
|
+
|
|
|
|
|
+ getThumbnailUrl: (fileId, filename) ->
|
|
|
|
|
+ # Генерация URL для thumbnail
|
|
|
|
|
+ return '/d/braer_color_shop/'+fileId+'/thumb-'+filename
|
|
|
|
|
+
|
|
|
|
|
+ attachFileToDocument: (fileId, targetDocId, targetType) ->
|
|
|
|
|
+ await @ensureInit()
|
|
|
|
|
+
|
|
|
|
|
+ try
|
|
|
|
|
+ log '📎 Прикрепление файла '+fileId+' к документу '+targetDocId
|
|
|
|
|
+
|
|
|
|
|
+ # Получение файла
|
|
|
|
|
+ # const file = await @pouchService.getDocument(fileId)
|
|
|
|
|
+
|
|
|
|
|
+ # Обновление списка прикрепленных документов
|
|
|
|
|
+ # if not file.attachedTo
|
|
|
|
|
+ # file.attachedTo = []
|
|
|
|
|
+ #
|
|
|
|
|
+ # file.attachedTo.push({
|
|
|
|
|
+ # docId: targetDocId
|
|
|
|
|
+ # type: targetType
|
|
|
|
|
+ # attachedAt: new Date().toISOString()
|
|
|
|
|
+ # })
|
|
|
|
|
+ #
|
|
|
|
|
+ # await @pouchService.saveDocument(file)
|
|
|
|
|
+
|
|
|
|
|
+ log '✅ Файл прикреплен к документу'
|
|
|
|
|
+ return true
|
|
|
|
|
+
|
|
|
|
|
+ catch error
|
|
|
|
|
+ log '❌ Ошибка прикрепления файла: '+error.message
|
|
|
|
|
+ throw error
|
|
|
|
|
+
|
|
|
|
|
+ detachFileFromDocument: (fileId, targetDocId) ->
|
|
|
|
|
+ await @ensureInit()
|
|
|
|
|
+
|
|
|
|
|
+ try
|
|
|
|
|
+ log '📎 Открепление файла '+fileId+' от документа '+targetDocId
|
|
|
|
|
+
|
|
|
|
|
+ # Получение файла
|
|
|
|
|
+ # const file = await @pouchService.getDocument(fileId)
|
|
|
|
|
+ #
|
|
|
|
|
+ # if file.attachedTo
|
|
|
|
|
+ # file.attachedTo = file.attachedTo.filter (attachment) ->
|
|
|
|
|
+ # attachment.docId != targetDocId
|
|
|
|
|
+ #
|
|
|
|
|
+ # await @pouchService.saveDocument(file)
|
|
|
|
|
+
|
|
|
|
|
+ log '✅ Файл откреплен от документа'
|
|
|
|
|
+ return true
|
|
|
|
|
+
|
|
|
|
|
+ catch error
|
|
|
|
|
+ log '❌ Ошибка открепления файла: '+error.message
|
|
|
|
|
+ throw error
|
|
|
|
|
+
|
|
|
|
|
+ getFilesByDocument: (docId) ->
|
|
|
|
|
+ await @ensureInit()
|
|
|
|
|
+
|
|
|
|
|
+ try
|
|
|
|
|
+ # В реальном приложении здесь будет запрос к PouchDB
|
|
|
|
|
+ # для поиска файлов, прикрепленных к указанному документу
|
|
|
|
|
+
|
|
|
|
|
+ # Временная заглушка
|
|
|
|
|
+ allFiles = await @getAllFiles()
|
|
|
|
|
+ # Фильтрация файлов, которые должны быть прикреплены к документу
|
|
|
|
|
+ attachedFiles = allFiles.filter (file) ->
|
|
|
|
|
+ file.attachedTo and file.attachedTo.some (attachment) ->
|
|
|
|
|
+ attachment.docId == docId
|
|
|
|
|
+
|
|
|
|
|
+ log '✅ Загружены файлы документа '+docId+': '+attachedFiles.length
|
|
|
|
|
+ return attachedFiles
|
|
|
|
|
+
|
|
|
|
|
+ catch error
|
|
|
|
|
+ log '❌ Ошибка загрузки файлов документа: '+error.message
|
|
|
|
|
+ throw error
|
|
|
|
|
+
|
|
|
|
|
+ ensureInit: ->
|
|
|
|
|
+ unless @initialized
|
|
|
|
|
+ throw new Error('MediaService не инициализирован. Вызовите init() сначала.')
|
|
|
|
|
+
|
|
|
|
|
+module.exports = new MediaService()
|