Compare commits
17 Commits
5ef8daf3e4
...
1a4653298d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a4653298d | ||
|
|
f770bd0e43 | ||
|
|
546ce83763 | ||
|
|
5f4f99245d | ||
|
|
c60d00f563 | ||
|
|
746367606e | ||
|
|
901259f5b8 | ||
|
|
f720c14ed1 | ||
|
|
c34f7c9bd9 | ||
|
|
af80214990 | ||
|
|
6bedfbc429 | ||
|
|
7e3f0dcede | ||
|
|
3a1635ec4d | ||
|
|
3e8c3968a0 | ||
|
|
269c7ea216 | ||
|
|
d7941ac862 | ||
|
|
09c1fbd1db |
130
AI_DRAWER_REDIS_SSE.md
Normal file
130
AI_DRAWER_REDIS_SSE.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# AI Drawer: Redis Pub/Sub вместо Polling
|
||||
|
||||
## ✅ Что сделано
|
||||
|
||||
### Проблема
|
||||
Раньше использовался polling - браузер каждые 2 секунды спрашивал сервер "готово ли?". Это создавало:
|
||||
- Лишние запросы к серверу
|
||||
- Задержку до 2 секунд перед получением ответа
|
||||
- Нагрузку на БД
|
||||
- Дублирование истории (БД + n8n)
|
||||
|
||||
### Решение
|
||||
Теперь используется **Redis Pub/Sub + SSE (Server-Sent Events)** с прямой публикацией из n8n:
|
||||
- ✅ Мгновенная доставка ответов (без задержек)
|
||||
- ✅ Нет лишних запросов (одно SSE соединение)
|
||||
- ✅ Меньше нагрузка на сервер
|
||||
- ✅ Нет дублирования - история только в n8n
|
||||
- ✅ Упрощенная архитектура - без БД и callback
|
||||
- ✅ Fallback на polling если SSE не работает
|
||||
|
||||
## 📋 Архитектура
|
||||
|
||||
```
|
||||
1. AI Drawer → n8n_proxy.php → возвращает task_id
|
||||
2. n8n обрабатывает → публикует ответ НАПРЯМУЮ в Redis
|
||||
3. Redis PUBLISH → канал "ai:response:{task_id}"
|
||||
4. ai_sse.php → подписан на Redis → отправляет через SSE в браузер
|
||||
5. Браузер → EventSource → получает ответ мгновенно! ⚡
|
||||
```
|
||||
|
||||
**История диалога:** Сохраняется в n8n автоматически (не дублируется в БД)
|
||||
|
||||
## 📁 Измененные файлы
|
||||
|
||||
### 1. `/aiassist/ai_sse.php` (новый)
|
||||
SSE endpoint для подписки на Redis события
|
||||
|
||||
### 2. `/aiassist/n8n_proxy.php` (упрощен)
|
||||
- ❌ Убрано сохранение в БД
|
||||
- ❌ Убран callback URL
|
||||
- ✅ Добавлены параметры Redis для n8n
|
||||
|
||||
### 3. `/callback_ai_response.php` (больше не используется)
|
||||
Можно удалить - n8n публикует напрямую в Redis
|
||||
|
||||
### 4. `/layouts/v7/resources/js/ai-drawer-simple.js` (обновлен)
|
||||
- Заменен `startPolling()` на `startSSEListener()`
|
||||
- Добавлен fallback на polling если SSE не работает
|
||||
- Добавлено поле `currentEventSource` для управления SSE соединением
|
||||
|
||||
## 🔧 Как работает
|
||||
|
||||
### Отправка запроса:
|
||||
```javascript
|
||||
// Пользователь отправляет сообщение
|
||||
sendToN8N(message) → получает task_id → startSSEListener(task_id)
|
||||
```
|
||||
|
||||
### Получение ответа:
|
||||
```javascript
|
||||
// SSE соединение открывается один раз
|
||||
EventSource('/aiassist/ai_sse.php?task_id=123')
|
||||
|
||||
// n8n обрабатывает и публикует НАПРЯМУЮ в Redis:
|
||||
Redis PUBLISH "ai:response:123" {
|
||||
task_id: "123",
|
||||
response: "...",
|
||||
status: "completed"
|
||||
}
|
||||
|
||||
// SSE endpoint получает событие и отправляет в браузер
|
||||
// Браузер получает ответ мгновенно!
|
||||
```
|
||||
|
||||
### Настройка n8n:
|
||||
См. подробную инструкцию: `N8N_REDIS_SETUP.md`
|
||||
|
||||
## 🛡️ Fallback механизм
|
||||
|
||||
Если SSE не работает (старые браузеры, проблемы с сетью):
|
||||
1. Через 5 секунд автоматически переключается на polling
|
||||
2. Использует старый метод `startPollingFallback()`
|
||||
3. Проверяет БД каждые 2 секунды
|
||||
|
||||
## ⚙️ Настройки Redis
|
||||
|
||||
- **Host**: `crm.clientright.ru`
|
||||
- **Port**: `6379`
|
||||
- **Password**: `CRM_Redis_Pass_2025_Secure!`
|
||||
- **Канал**: `ai:response:{task_id}`
|
||||
|
||||
## 🧪 Тестирование
|
||||
|
||||
1. Откройте AI Drawer в CRM
|
||||
2. Отправьте сообщение
|
||||
3. Проверьте консоль браузера:
|
||||
- `AI Drawer: SSE connection opened`
|
||||
- `AI Drawer: Received response via SSE`
|
||||
4. Ответ должен прийти мгновенно после обработки n8n
|
||||
|
||||
## 📊 Преимущества
|
||||
|
||||
| Параметр | Polling (старое) | Redis Pub/Sub (новое) |
|
||||
|----------|------------------|----------------------|
|
||||
| Скорость | До 2 сек задержки | Мгновенно ⚡ |
|
||||
| Запросы | Каждые 2 сек | Одно соединение |
|
||||
| Нагрузка | Высокая | Низкая |
|
||||
| Надежность | ✅ | ✅ + fallback |
|
||||
|
||||
## 🔍 Отладка
|
||||
|
||||
### Проверить Redis публикацию:
|
||||
```bash
|
||||
redis-cli -h crm.clientright.ru -a 'CRM_Redis_Pass_2025_Secure!' \
|
||||
PUBLISH "ai:response:test-task" '{"task_id":"test-task","response":"test"}'
|
||||
```
|
||||
|
||||
### Проверить SSE endpoint:
|
||||
```bash
|
||||
curl -N "https://crm.clientright.ru/aiassist/ai_sse.php?task_id=test-task"
|
||||
```
|
||||
|
||||
### Логи:
|
||||
- PHP error_log: `/var/log/php/error.log`
|
||||
- Ищите: `[AI SSE]` и `[Callback]`
|
||||
|
||||
## ✅ Результат
|
||||
|
||||
Теперь AI Drawer получает ответы **мгновенно** через Redis Pub/Sub вместо ожидания polling каждые 2 секунды!
|
||||
|
||||
89
API_ATTACH_DOCS_README.md
Normal file
89
API_ATTACH_DOCS_README.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# 📎 API для привязки документов
|
||||
|
||||
## ✅ Прямой эндпоинт (готов к использованию!)
|
||||
|
||||
```
|
||||
POST https://crm.clientright.ru/api_attach_documents.php
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Формат запроса (из n8n)
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"contact_id": "320096",
|
||||
"project_id": "396868",
|
||||
"ticket_id": "396936",
|
||||
"filename": "boarding_pass.pdf",
|
||||
"file_type": "flight_delay_boarding_or_ticket",
|
||||
"file": "/f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c/crm2/CRM_Active_Files/Documents/HelpDesk/ЗАЯВКА_827_396936/flight_delay_boarding_or_ticket.pdf"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Важно:**
|
||||
- ✅ Массив `[...]` (даже для одного документа)
|
||||
- ✅ `file` без хоста → автоматически добавится `https://s3.twcstorage.ru`
|
||||
- ✅ `ticket_id` опционально (если есть → HelpDesk, иначе → Project)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Формат ответа
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"total_processed": 1,
|
||||
"successful": 1,
|
||||
"failed": 0,
|
||||
"results": [
|
||||
{
|
||||
"document_id": "15x396941",
|
||||
"document_numeric_id": "396941",
|
||||
"attached_to": "ticket",
|
||||
"attached_to_id": "396936",
|
||||
"file_name": "boarding_pass.pdf",
|
||||
"file_type": "flight_delay_boarding_or_ticket",
|
||||
"s3_bucket": "f9825c87-...",
|
||||
"s3_key": "crm2/CRM_Active_Files/...",
|
||||
"file_size": 85320,
|
||||
"message": "Документ создан и привязан..."
|
||||
}
|
||||
],
|
||||
"errors": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Тест
|
||||
|
||||
```bash
|
||||
curl -X POST "https://crm.clientright.ru/api_attach_documents.php" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '[
|
||||
{
|
||||
"contact_id": "320096",
|
||||
"project_id": "396868",
|
||||
"ticket_id": "396936",
|
||||
"filename": "test.pdf",
|
||||
"file_type": "flight_delay_boarding_or_ticket",
|
||||
"file": "/bucket/path/to/file.pdf"
|
||||
}
|
||||
]'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Логи
|
||||
|
||||
```bash
|
||||
tail -f /var/www/fastuser/data/www/crm.clientright.ru/logs/api_attach_documents.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Готово к использованию в n8n!
|
||||
|
||||
236
CREATE_WEB_PROJECT_DOCS.md
Normal file
236
CREATE_WEB_PROJECT_DOCS.md
Normal file
@@ -0,0 +1,236 @@
|
||||
# CreateWebProject - Документация
|
||||
|
||||
**Дата создания:** 01.11.2025
|
||||
**Автор:** Фёдор
|
||||
**Статус:** ✅ Работает и протестировано
|
||||
|
||||
---
|
||||
|
||||
## 📋 Назначение
|
||||
|
||||
Операция vTiger webservice для создания проекта из web-формы ERV.
|
||||
|
||||
**Логика работы:**
|
||||
- Если проект с таким номером полиса **существует** → возвращает ID **БЕЗ обновления**
|
||||
- Если проект **не существует** → создаёт новый
|
||||
|
||||
По аналогии с `CreateWebContact`.
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Параметры
|
||||
|
||||
### Обязательные:
|
||||
1. **policy_number** (String) - Номер полиса ERV (cf_1885)
|
||||
- Пример: `E1000-123456789`
|
||||
|
||||
2. **contact_id** (String) - ID контакта для привязки
|
||||
- Пример: `396625`
|
||||
- Получается из `CreateWebContact` на шаге 1
|
||||
|
||||
### Опциональные:
|
||||
3. **period_start** (String) - Дата начала страхования (cf_1887)
|
||||
- Формат: `DD-MM-YYYY`
|
||||
- Пример: `01-01-2025`
|
||||
|
||||
4. **period_end** (String) - Дата окончания страхования (cf_1889)
|
||||
- Формат: `DD-MM-YYYY`
|
||||
- Пример: `31-12-2025`
|
||||
|
||||
---
|
||||
|
||||
## 📥 Пример запроса
|
||||
|
||||
```bash
|
||||
curl -X POST "https://crm.clientright.ru/webservice.php" \
|
||||
-d "operation=CreateWebProject" \
|
||||
-d "sessionName={token от login}" \
|
||||
-d "policy_number=E1000-123456789" \
|
||||
-d "contact_id=396625" \
|
||||
-d "period_start=01-01-2025" \
|
||||
-d "period_end=31-12-2025"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📤 Пример ответа
|
||||
|
||||
### Новый проект (создан):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": "{\"project_id\":\"396865\",\"is_new\":true}"
|
||||
}
|
||||
```
|
||||
|
||||
### Существующий проект (найден):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": "{\"project_id\":\"396865\",\"is_new\":false}"
|
||||
}
|
||||
```
|
||||
|
||||
**Важно:** `result` - это JSON-строка, требует `JSON.parse()`!
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Создаваемые поля проекта
|
||||
|
||||
При создании нового проекта заполняются:
|
||||
|
||||
| Поле | Значение | Примечание |
|
||||
|------|----------|------------|
|
||||
| `projectname` | `ERV {полис} цифровой адвокат` | Например: "ERV E1000-123456789 цифровой адвокат" |
|
||||
| `projectstatus` | `модерация` | Статус |
|
||||
| `projecttype` | `ерв урегулирование` | Тип проекта |
|
||||
| `linktoaccountscontacts` | `12x{contact_id}` | Привязка к контакту |
|
||||
| `cf_1994` | `11x67458` | Заявитель (контрагент) |
|
||||
| `cf_1885` | `{policy_number}` | Номер полиса ⭐ |
|
||||
| `cf_1887` | `{period_start}` | Период начала (опционально) |
|
||||
| `cf_1889` | `{period_end}` | Период конца (опционально) |
|
||||
| `assigned_user_id` | Текущий API user | Ответственный |
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Логика поиска
|
||||
|
||||
Проект ищется в БД по запросу:
|
||||
|
||||
```sql
|
||||
SELECT p.projectid
|
||||
FROM vtiger_project p
|
||||
INNER JOIN vtiger_projectcf pcf ON p.projectid = pcf.projectid
|
||||
LEFT JOIN vtiger_crmentity e ON e.crmid = p.projectid
|
||||
WHERE e.deleted = 0 AND pcf.cf_1885 = '{policy_number}'
|
||||
LIMIT 1
|
||||
```
|
||||
|
||||
**Поиск по:** `cf_1885` (номер полиса)
|
||||
|
||||
---
|
||||
|
||||
## 📁 Файлы
|
||||
|
||||
- **Handler:** `include/Webservices/CreateWebProject.php`
|
||||
- **Handler Method:** `vtws_createwebproject`
|
||||
- **Логи:** `logs/CreateWebProject.log`
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ Регистрация в БД
|
||||
|
||||
### vtiger_ws_operation:
|
||||
```
|
||||
operationid: 51
|
||||
name: CreateWebProject
|
||||
handler_path: include/Webservices/CreateWebProject.php
|
||||
handler_method: vtws_createwebproject
|
||||
type: POST
|
||||
prelogin: 0
|
||||
```
|
||||
|
||||
### vtiger_ws_operation_parameters:
|
||||
```
|
||||
1. policy_number (String)
|
||||
2. contact_id (String)
|
||||
3. period_start (String)
|
||||
4. period_end (String)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Тестирование
|
||||
|
||||
### Тест 1: Создание нового проекта
|
||||
```
|
||||
Полис: E1000-TEST-1761990646
|
||||
Contact: 396625
|
||||
Результат: {"project_id":"396865","is_new":true}
|
||||
Статус: ✅ Создан
|
||||
```
|
||||
|
||||
### Тест 2: Повторный вызов с тем же полисом
|
||||
```
|
||||
Полис: E1000-TEST-1761990646
|
||||
Contact: 396625
|
||||
Результат: {"project_id":"396865","is_new":false}
|
||||
Статус: ✅ Найден существующий (НЕ создан дубликат!)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Интеграция с n8n
|
||||
|
||||
### Workflow флоу (планируется):
|
||||
|
||||
```
|
||||
1. Step1Phone → CreateWebContact
|
||||
↓ contact_id
|
||||
|
||||
2. Step2Policy → Проверка полиса
|
||||
↓ policy_number, period_start, period_end
|
||||
|
||||
3. n8n → CreateWebProject
|
||||
POST https://crm.clientright.ru/webservice.php
|
||||
{
|
||||
operation: CreateWebProject,
|
||||
sessionName: {token},
|
||||
policy_number: "E1000-123456789",
|
||||
contact_id: "396625",
|
||||
period_start: "01-01-2025",
|
||||
period_end: "31-12-2025"
|
||||
}
|
||||
↓
|
||||
|
||||
4. Response → {"project_id": "396865", "is_new": false}
|
||||
↓
|
||||
|
||||
5. Redis session update:
|
||||
claim:{claim_id} += {
|
||||
project_id: "396865",
|
||||
is_new_project: false
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Связь с CreateWebContact
|
||||
|
||||
| Операция | Ищет по | Создаёт если | Возвращает |
|
||||
|----------|---------|--------------|------------|
|
||||
| `CreateWebContact` | `mobile` (телефон) | Не найден контакт | `{contact_id, is_new}` |
|
||||
| `CreateWebProject` | `cf_1885` (полис) | Не найден проект | `{project_id, is_new}` |
|
||||
|
||||
**Флоу:** Телефон → Контакт → Полис → Проект → Тип события → Документы → Тикет
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Ошибка: "Unknown operation requested"
|
||||
**Причина:** Операция не зарегистрирована в БД.
|
||||
**Решение:** Запустить `register_CreateWebProject_fixed.php` (уже выполнено).
|
||||
|
||||
### Ошибка: BOM символ в ответе
|
||||
**Причина:** Файл CreateWebProject.php сохранён с UTF-8 BOM.
|
||||
**Решение:** `sed -i '1s/^\xEF\xBB\xBF//' include/Webservices/CreateWebProject.php` (уже выполнено).
|
||||
|
||||
### Проект создаётся дубликатом
|
||||
**Причина:** Номер полиса отличается (пробелы, регистр).
|
||||
**Решение:** В коде есть `trim()`, но нет приведения к верхнему регистру. Если нужно - добавить.
|
||||
|
||||
---
|
||||
|
||||
## 📝 История изменений
|
||||
|
||||
**01.11.2025:**
|
||||
- ✅ Создан файл CreateWebProject.php
|
||||
- ✅ Зарегистрирован в БД (operationid: 51)
|
||||
- ✅ Протестирован (создание + поиск)
|
||||
- ✅ Закоммичен в master (af802149)
|
||||
|
||||
---
|
||||
|
||||
**Готово к использованию!** 🎉
|
||||
|
||||
67
FINAL_STEPS.sh
Normal file
67
FINAL_STEPS.sh
Normal file
@@ -0,0 +1,67 @@
|
||||
#!/bin/bash
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🚀 ФИНАЛЬНЫЕ ШАГИ - Сохранение и перезапуск"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# 1. Git Commit
|
||||
echo "📦 Шаг 1: Git Commit..."
|
||||
cd /var/www/fastuser/data/www/crm.clientright.ru/erv_platform
|
||||
git add -A
|
||||
git commit -m "fix: OCR endpoint исправлен /process → /analyze-file
|
||||
|
||||
Проблема:
|
||||
❌ POST http://147.45.146.17:8001/process → 404 Not Found
|
||||
❌ OCR не работал
|
||||
❌ Gemini Vision не получал данные
|
||||
|
||||
Решение:
|
||||
✅ Endpoint изменен на /analyze-file (правильный)
|
||||
✅ Исправлено в 3 местах
|
||||
✅ Добавлено детальное логирование
|
||||
|
||||
Теперь OCR работает!"
|
||||
|
||||
git push origin main
|
||||
echo "✅ Git push выполнен!"
|
||||
echo ""
|
||||
|
||||
# 2. Перезапуск Backend
|
||||
echo "🔄 Шаг 2: Перезапуск Backend..."
|
||||
pkill -9 -f "uvicorn app.main"
|
||||
sleep 2
|
||||
|
||||
cd /var/www/fastuser/data/www/crm.clientright.ru/erv_platform/backend
|
||||
source venv/bin/activate
|
||||
nohup python -m uvicorn app.main:app --host 0.0.0.0 --port 8100 > ../../erv_platform_backend.log 2>&1 &
|
||||
sleep 3
|
||||
|
||||
echo "✅ Backend перезапущен!"
|
||||
echo ""
|
||||
|
||||
# 3. Проверка
|
||||
echo "🔍 Шаг 3: Проверка сервисов..."
|
||||
curl -s http://localhost:8100/health | python3 -m json.tool
|
||||
echo ""
|
||||
|
||||
docker ps | grep frontend
|
||||
echo ""
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "✅ ВСЁ ГОТОВО!"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "🌐 Открывай и тестируй:"
|
||||
echo " http://147.45.146.17:5173"
|
||||
echo ""
|
||||
echo "🔍 Смотри логи:"
|
||||
echo " tail -f /var/www/fastuser/data/www/crm.clientright.ru/erv_platform_backend.log"
|
||||
echo ""
|
||||
echo "При загрузке файла увидишь:"
|
||||
echo " 🔍 Starting OCR for: filename"
|
||||
echo " 📄 OCR completed: XXX chars"
|
||||
echo " 🤖 Starting AI analysis"
|
||||
echo " ✅ AI: policy, confidence: 95%"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
155
N8N_REDIS_FIX.md
Normal file
155
N8N_REDIS_FIX.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# 🔧 Исправление конфигурации n8n для Redis публикации
|
||||
|
||||
## ❌ Проблема в текущей конфигурации
|
||||
|
||||
```json
|
||||
{
|
||||
"channel": "=ai:response:{{ $('Edit Fields').item.json.taskId }}",
|
||||
"messageData": "={{ JSON.stringify($json.output) }}"
|
||||
}
|
||||
```
|
||||
|
||||
**Проблемы:**
|
||||
1. ❌ Канал использует `$('Edit Fields').item.json.taskId` - неправильный путь
|
||||
2. ❌ `messageData` содержит `$json.output` - неправильный формат
|
||||
3. ❌ Нет сохранения в Redis ключ для fallback
|
||||
|
||||
## ✅ Правильная конфигурация
|
||||
|
||||
### Вариант 1: Если taskId в корне webhook body
|
||||
|
||||
**Channel:**
|
||||
```
|
||||
ai:response:{{ $json.taskId }}
|
||||
```
|
||||
|
||||
**Message (JSON объект):**
|
||||
```json
|
||||
{
|
||||
"task_id": "{{ $json.taskId }}",
|
||||
"response": "{{ $json.output }}",
|
||||
"status": "completed"
|
||||
}
|
||||
```
|
||||
|
||||
### Вариант 2: Если taskId в webhook.body
|
||||
|
||||
**Channel:**
|
||||
```
|
||||
ai:response:{{ $json.webhook.body.taskId }}
|
||||
```
|
||||
|
||||
**Message (JSON объект):**
|
||||
```json
|
||||
{
|
||||
"task_id": "{{ $json.webhook.body.taskId }}",
|
||||
"response": "{{ $json.output }}",
|
||||
"status": "completed"
|
||||
}
|
||||
```
|
||||
|
||||
### Вариант 3: Если ответ в другой ноде (например, AI Chat)
|
||||
|
||||
**Channel:**
|
||||
```
|
||||
{{ $json.webhook.body.redisChannel }}
|
||||
```
|
||||
|
||||
**Message (JSON объект):**
|
||||
```json
|
||||
{
|
||||
"task_id": "{{ $json.webhook.body.taskId }}",
|
||||
"response": "{{ $json['AI Chat'].json.response }}",
|
||||
"status": "completed"
|
||||
}
|
||||
```
|
||||
|
||||
## 📋 Полная настройка n8n workflow
|
||||
|
||||
### Шаг 1: Redis SET (сохранить в ключ для fallback)
|
||||
|
||||
**Operation:** `Set`
|
||||
**Key:** `ai:response:cache:{{ $json.webhook.body.taskId }}`
|
||||
**Value:**
|
||||
```json
|
||||
{
|
||||
"task_id": "{{ $json.webhook.body.taskId }}",
|
||||
"response": "{{ $json['AI Chat'].json.response }}",
|
||||
"status": "completed",
|
||||
"timestamp": "{{ $now.toISO() }}"
|
||||
}
|
||||
```
|
||||
**TTL:** `300` секунд
|
||||
|
||||
### Шаг 2: Redis PUBLISH (опубликовать в канал для SSE)
|
||||
|
||||
**Operation:** `Publish`
|
||||
**Channel:** `{{ $json.webhook.body.redisChannel }}`
|
||||
**Message:**
|
||||
```json
|
||||
{
|
||||
"task_id": "{{ $json.webhook.body.taskId }}",
|
||||
"response": "{{ $json['AI Chat'].json.response }}",
|
||||
"status": "completed"
|
||||
}
|
||||
```
|
||||
|
||||
## 🔍 Как найти правильный путь к данным
|
||||
|
||||
1. **Добавьте ноду "Set" перед Redis:**
|
||||
- Сохраните все данные из предыдущих нод
|
||||
- Посмотрите структуру данных в n8n
|
||||
|
||||
2. **Используйте Expression Editor в n8n:**
|
||||
- Нажмите на поле "Channel" или "Message"
|
||||
- Выберите "Expression"
|
||||
- Начните вводить `$json.` - увидите доступные поля
|
||||
|
||||
3. **Проверьте webhook body:**
|
||||
- В ноде Webhook посмотрите что приходит
|
||||
- `taskId` и `redisChannel` должны быть в `$json.webhook.body`
|
||||
|
||||
## ✅ Проверка
|
||||
|
||||
После настройки проверьте:
|
||||
|
||||
1. **В n8n:**
|
||||
- Запустите workflow
|
||||
- Проверьте что Redis ноды выполнились успешно
|
||||
- Посмотрите что именно публикуется в канал
|
||||
|
||||
2. **В Redis:**
|
||||
```bash
|
||||
redis-cli -h crm.clientright.ru -p 6379 -a 'CRM_Redis_Pass_2025_Secure!' \
|
||||
MONITOR
|
||||
```
|
||||
Должны видеть:
|
||||
- `SET ai:response:cache:task-xxx ...`
|
||||
- `PUBLISH ai:response:task-xxx ...`
|
||||
|
||||
3. **В браузере:**
|
||||
- Откройте консоль (F12)
|
||||
- Должны видеть: `AI Drawer: SSE connection opened`
|
||||
- Должны видеть: `AI Drawer: Received response via SSE`
|
||||
|
||||
## 🐛 Отладка
|
||||
|
||||
Если не работает:
|
||||
|
||||
1. **Проверьте формат сообщения:**
|
||||
- Должен быть валидный JSON
|
||||
- Должно быть поле `response` или `task_id`
|
||||
|
||||
2. **Проверьте канал:**
|
||||
- Должен совпадать с `redisChannel` из `n8n_proxy.php`
|
||||
- Формат: `ai:response:task-xxx`
|
||||
|
||||
3. **Проверьте логи PHP:**
|
||||
```bash
|
||||
tail -f /var/log/apache2/error.log | grep "AI SSE"
|
||||
```
|
||||
|
||||
4. **Проверьте что SSE endpoint доступен:**
|
||||
- Откройте в браузере: `/aiassist/ai_sse.php?task_id=test-123`
|
||||
- Должен открыться поток SSE (не ошибка)
|
||||
|
||||
185
N8N_REDIS_SETUP.md
Normal file
185
N8N_REDIS_SETUP.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# Настройка n8n для прямой публикации в Redis
|
||||
|
||||
## 🎯 Цель
|
||||
Настроить n8n workflow так, чтобы после обработки AI ответа он публиковал результат **напрямую в Redis**, без промежуточного callback.
|
||||
|
||||
## 📋 Архитектура
|
||||
|
||||
```
|
||||
AI Drawer → n8n_proxy.php → n8n webhook
|
||||
↓
|
||||
[Обработка AI]
|
||||
↓
|
||||
Redis PUBLISH → ai:response:{taskId}
|
||||
↓
|
||||
SSE → браузер получает мгновенно! ⚡
|
||||
```
|
||||
|
||||
## 🔧 Настройка n8n Workflow
|
||||
|
||||
### Шаг 1: Добавить Redis ноду после обработки AI
|
||||
|
||||
В вашем n8n workflow после ноды обработки AI добавьте **Redis ноду**:
|
||||
|
||||
1. **Тип ноды**: `Redis`
|
||||
2. **Operation**: `Publish`
|
||||
|
||||
### Шаг 2: Настройки Redis ноды
|
||||
|
||||
**Connection:**
|
||||
```
|
||||
Host: crm.clientright.ru
|
||||
Port: 6379
|
||||
Password: CRM_Redis_Pass_2025_Secure!
|
||||
Database: 0
|
||||
```
|
||||
|
||||
**Operation Settings:**
|
||||
```
|
||||
Operation: Publish
|
||||
Channel: {{ $json.redisChannel }}
|
||||
```
|
||||
|
||||
**Message (вариант 1 - JSON объект, рекомендуется):**
|
||||
```json
|
||||
{
|
||||
"task_id": "{{ $json.taskId }}",
|
||||
"status": "completed",
|
||||
"response": "{{ $json.aiResponse }}",
|
||||
"timestamp": "{{ $now.toISO() }}"
|
||||
}
|
||||
```
|
||||
|
||||
**Message (вариант 2 - просто текст, тоже работает):**
|
||||
```
|
||||
{{ $json.aiResponse }}
|
||||
```
|
||||
|
||||
⚠️ **Важно:** SSE endpoint поддерживает оба формата:
|
||||
- JSON объект с полем `response` - предпочтительно
|
||||
- Просто текст ответа - тоже работает (автоматически обрабатывается)
|
||||
|
||||
### Шаг 2.5: Сохранение в Redis ключ (ВАЖНО для fallback)
|
||||
|
||||
⚠️ **КРИТИЧНО:** Сохраняйте ответ в Redis ключ **ПЕРЕД** публикацией в канал!
|
||||
|
||||
**Порядок действий в n8n:**
|
||||
1. Обработка AI → получен ответ
|
||||
2. **Сначала:** Redis SET → сохранить в ключ `ai:response:cache:{taskId}` (TTL 300 сек)
|
||||
3. **Потом:** Redis PUBLISH → опубликовать в канал `ai:response:{taskId}`
|
||||
|
||||
**Добавьте Redis ноду для SET (перед PUBLISH):**
|
||||
|
||||
**Operation:** `Set`
|
||||
**Key:** `ai:response:cache:{{ $json.taskId }}`
|
||||
**Value:**
|
||||
```json
|
||||
{
|
||||
"task_id": "{{ $json.taskId }}",
|
||||
"response": "{{ $json.aiResponse }}",
|
||||
"status": "completed",
|
||||
"timestamp": "{{ $now.toISO() }}"
|
||||
}
|
||||
```
|
||||
**TTL:** `300` секунд (5 минут)
|
||||
|
||||
**Зачем это нужно:**
|
||||
- Если SSE не подписался вовремя → fallback найдет ответ в ключе
|
||||
- Если браузер перезагрузился → ответ все еще доступен
|
||||
- Надежность: двойное сохранение (канал + ключ)
|
||||
|
||||
### Шаг 3: Канал Redis
|
||||
|
||||
Канал формируется автоматически из `taskId`:
|
||||
```
|
||||
ai:response:{{ $json.taskId }}
|
||||
```
|
||||
|
||||
Или используйте значение из входящего запроса:
|
||||
```
|
||||
{{ $json.redisChannel }}
|
||||
```
|
||||
|
||||
## 📝 Пример workflow
|
||||
|
||||
```
|
||||
[Webhook] → [AI обработка] → [Redis SET] → [Redis PUBLISH] → [End]
|
||||
↓ ↓
|
||||
[Сохранить историю в n8n] [Ответ в ключе + канале]
|
||||
```
|
||||
|
||||
**Порядок:**
|
||||
1. SET в ключ `ai:response:cache:{taskId}` (для fallback)
|
||||
2. PUBLISH в канал `ai:response:{taskId}` (для SSE)
|
||||
3. Сохранение истории в n8n
|
||||
|
||||
### Детали Redis ноды:
|
||||
|
||||
**Input:**
|
||||
- `taskId` - из входящего webhook запроса
|
||||
- `aiResponse` - результат обработки AI
|
||||
- `redisChannel` - канал из входящего запроса (`ai:response:{taskId}`)
|
||||
|
||||
**Output:**
|
||||
- Публикация в Redis канал
|
||||
- Браузер получает через SSE мгновенно
|
||||
|
||||
## ✅ Проверка
|
||||
|
||||
### Тест публикации из командной строки:
|
||||
```bash
|
||||
redis-cli -h crm.clientright.ru -p 6379 -a 'CRM_Redis_Pass_2025_Secure!' \
|
||||
PUBLISH "ai:response:test-task" '{"task_id":"test-task","response":"Тест","status":"completed"}'
|
||||
```
|
||||
|
||||
### Проверка в n8n:
|
||||
1. Запустите workflow с тестовым запросом
|
||||
2. Проверьте логи Redis ноды - должна быть успешная публикация
|
||||
3. В браузере откройте AI Drawer и отправьте сообщение
|
||||
4. Ответ должен прийти мгновенно через SSE
|
||||
|
||||
## 🔍 Отладка
|
||||
|
||||
### Если ответ не приходит:
|
||||
|
||||
1. **Проверьте канал Redis:**
|
||||
```bash
|
||||
redis-cli -h crm.clientright.ru -p 6379 -a 'CRM_Redis_Pass_2025_Secure!' \
|
||||
MONITOR
|
||||
```
|
||||
Должны видеть PUBLISH команды
|
||||
|
||||
2. **Проверьте формат сообщения:**
|
||||
Сообщение должно быть валидным JSON:
|
||||
```json
|
||||
{
|
||||
"task_id": "task-123",
|
||||
"response": "Ответ от AI",
|
||||
"status": "completed"
|
||||
}
|
||||
```
|
||||
|
||||
3. **Проверьте SSE endpoint:**
|
||||
```bash
|
||||
curl -N "https://crm.clientright.ru/aiassist/ai_sse.php?task_id=test-task"
|
||||
```
|
||||
|
||||
## 📊 Преимущества новой архитектуры
|
||||
|
||||
✅ **Проще** - нет промежуточного callback
|
||||
✅ **Быстрее** - прямая публикация в Redis
|
||||
✅ **Надежнее** - меньше точек отказа
|
||||
✅ **Меньше кода** - убрали БД и callback
|
||||
|
||||
## 🚨 Важно
|
||||
|
||||
- История диалога сохраняется в n8n автоматически (не нужно дублировать в БД)
|
||||
- Если Redis недоступен, браузер автоматически переключится на fallback (polling)
|
||||
- Канал Redis уникален для каждого запроса: `ai:response:{taskId}`
|
||||
|
||||
## 📁 Связанные файлы
|
||||
|
||||
- `/aiassist/n8n_proxy.php` - отправляет запрос в n8n с параметрами Redis
|
||||
- `/aiassist/ai_sse.php` - SSE endpoint для получения ответов из Redis
|
||||
- `/layouts/v7/resources/js/ai-drawer-simple.js` - JavaScript клиент с SSE
|
||||
|
||||
223
NEXTCLOUD_ИТОГОВОЕ_РЕШЕНИЕ.md
Normal file
223
NEXTCLOUD_ИТОГОВОЕ_РЕШЕНИЕ.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# ✅ NEXTCLOUD - ИТОГОВОЕ РЕШЕНИЕ МЕДЛЕННОЙ РАБОТЫ
|
||||
|
||||
**Дата:** 31 октября 2025
|
||||
**Проблема:** Медленное открытие файлов для редактирования (25 секунд)
|
||||
**Решение:** Найдено и применено
|
||||
|
||||
---
|
||||
|
||||
## 🔍 НАЙДЕННАЯ ПРОБЛЕМА
|
||||
|
||||
### **Корневая причина:**
|
||||
|
||||
При запросе **одного файла** через PROPFIND, Nextcloud с настройкой `filesystem_check_changes=1`:
|
||||
|
||||
1. Проверяет кеш для файла
|
||||
2. **НЕ находит актуальные данные**
|
||||
3. Решает обновить кеш
|
||||
4. **СКАНИРУЕТ ВСЮ РОДИТЕЛЬСКУЮ ПАПКУ** `Documents/Project/`
|
||||
5. Вызывает `getDirectoryContent()` для S3
|
||||
6. **Делает LIST запрос к S3 для ~700 подпапок проектов**
|
||||
7. Каждая подпапка - отдельный HTTP запрос
|
||||
8. **700 запросов × 30ms latency = 21+ секунда**
|
||||
|
||||
### **Почему так происходило:**
|
||||
|
||||
```php
|
||||
// AmazonS3.php line 648
|
||||
function getDirectoryMetaData($path) {
|
||||
// Сканирует ВСЮ папку, а не только запрошенный файл!
|
||||
return $this->getDirectoryContent($path);
|
||||
}
|
||||
```
|
||||
|
||||
При `filesystem_check_changes=1` Nextcloud хотел убедиться что файл актуален, но вместо проверки одного файла - сканировал всю папку с сотнями проектов!
|
||||
|
||||
---
|
||||
|
||||
## ✅ РЕШЕНИЕ
|
||||
|
||||
### **Отключить filesystem_check для S3 External Storage:**
|
||||
|
||||
```bash
|
||||
docker exec -u www-data nextcloud-fresh php occ files_external:option 1 filesystem_check_changes 0
|
||||
```
|
||||
|
||||
### **Результаты:**
|
||||
|
||||
| Показатель | До | После | Улучшение |
|
||||
|------------|-----|--------|-----------|
|
||||
| **PROPFIND время** | 24-26 сек | 1.3-1.7 сек | **-94%** 🚀 |
|
||||
| **Ускорение** | - | - | **x18** |
|
||||
| **Стабильность** | Скачет | Стабильно | ✅ |
|
||||
|
||||
### **Побочные эффекты:**
|
||||
|
||||
⚠️ **Новые файлы НЕ видны автоматически**
|
||||
|
||||
**Решение:** Периодическое сканирование через cron:
|
||||
```bash
|
||||
# Каждые 30 минут - сканируем только новые файлы
|
||||
*/30 * * * * docker exec -u www-data nextcloud-fresh php occ files:scan --path=/admin/files/crm/crm2/CRM_Active_Files/Documents/Project --shallow
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 ТЕСТЫ ПРОИЗВОДИТЕЛЬНОСТИ
|
||||
|
||||
### Серия из 5 тестов:
|
||||
```
|
||||
Тест 1: 1.722s
|
||||
Тест 2: 1.496s
|
||||
Тест 3: 1.339s ← лучший
|
||||
Тест 4: 1.447s
|
||||
Тест 5: 1.452s
|
||||
|
||||
Среднее: 1.49 секунды
|
||||
```
|
||||
|
||||
**Стабильная скорость:** ~1.5 секунды
|
||||
|
||||
---
|
||||
|
||||
## 🎯 ДОПОЛНИТЕЛЬНЫЕ ОПТИМИЗАЦИИ
|
||||
|
||||
### Что уже сделано:
|
||||
|
||||
1. ✅ **Redis Distributed Cache** - кеш между запросами
|
||||
2. ✅ **PHP Memory 1024MB** - больше памяти
|
||||
3. ✅ **OPcache оптимизирован** - меньше компиляций
|
||||
4. ✅ **MySQL max_connections=300** - больше соединений
|
||||
5. ✅ **Шифрование отключено** - меньше CPU
|
||||
6. ✅ **filesystem_check_changes=0** - без сканирования S3
|
||||
|
||||
### CPU/Memory после всех оптимизаций:
|
||||
|
||||
```
|
||||
Nextcloud: 0.05-5% CPU (было 50%)
|
||||
Memory: 643-746 MB (было 1005 MB)
|
||||
Deadlocks: минимум (было много)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 ДАЛЬНЕЙШИЕ УЛУЧШЕНИЯ (опционально)
|
||||
|
||||
### Если нужно ещё быстрее (< 1 секунды):
|
||||
|
||||
#### **#1: Nginx Reverse Proxy с кешем метаданных**
|
||||
|
||||
```nginx
|
||||
# Кешировать PROPFIND ответы
|
||||
location ~ ^/remote.php/dav/files/ {
|
||||
proxy_cache_path /var/cache/nginx/propfind levels=1:2 keys_zone=PROPFIND:10m;
|
||||
proxy_cache PROPFIND;
|
||||
proxy_cache_valid 200 5m;
|
||||
proxy_cache_methods GET HEAD PROPFIND;
|
||||
proxy_cache_key "$request_method$request_uri";
|
||||
|
||||
proxy_pass http://nextcloud-fresh;
|
||||
}
|
||||
```
|
||||
|
||||
**Эффект:** Метаданные из Nginx кеша → 0.01-0.05 секунды
|
||||
|
||||
---
|
||||
|
||||
#### **#2: Прямой WOPI endpoint в CRM (обход Nextcloud для метаданных)**
|
||||
|
||||
```php
|
||||
// Collabora запрашивает метаданные напрямую из CRM
|
||||
// CRM отдаёт из своей БД (мгновенно!)
|
||||
// Collabora читает файл через presigned S3 URL
|
||||
|
||||
WOPISrc = https://crm.clientright.ru/wopi/files/{recordId}
|
||||
```
|
||||
|
||||
**Эффект:** 0.1-0.2 секунды полное открытие
|
||||
|
||||
---
|
||||
|
||||
#### **#3: Кеш в CRM Redis**
|
||||
|
||||
```php
|
||||
// Кешировать fileId в CRM Redis
|
||||
$redis->set("nc_fileid:$recordId", $fileId, 3600);
|
||||
|
||||
// При открытии - сразу из кеша
|
||||
$fileId = $redis->get("nc_fileid:$recordId");
|
||||
if ($fileId) {
|
||||
// Сразу открываем без PROPFIND
|
||||
return "https://office.clientright.ru:8443/apps/richdocuments/index?fileId=$fileId";
|
||||
}
|
||||
```
|
||||
|
||||
**Эффект:** Без PROPFIND вообще → 0.05-0.1 секунды
|
||||
|
||||
---
|
||||
|
||||
## 🎯 ИТОГОВЫЕ РЕКОМЕНДАЦИИ
|
||||
|
||||
### **ДЛЯ ПРОДАКШЕНА (СЕЙЧАС):**
|
||||
|
||||
```bash
|
||||
# 1. filesystem_check_changes=0 для S3 (УЖЕ ПРИМЕНЕНО)
|
||||
docker exec -u www-data nextcloud-fresh php occ files_external:option 1 filesystem_check_changes 0
|
||||
|
||||
# 2. Cron сканирование каждые 30 минут
|
||||
crontab -e
|
||||
*/30 * * * * docker exec -u www-data nextcloud-fresh php occ files:scan --path=/admin/files/crm --shallow > /dev/null 2>&1
|
||||
|
||||
# 3. Мониторинг
|
||||
docker stats nextcloud-fresh --no-stream
|
||||
```
|
||||
|
||||
### **ЕСЛИ НУЖНО ЕЩЁ БЫСТРЕЕ:**
|
||||
|
||||
- Nginx cache для PROPFIND (→ 0.05s)
|
||||
- Кеш fileId в CRM Redis (→ без PROPFIND)
|
||||
- Прямой WOPI от CRM (→ полный обход Nextcloud для метаданных)
|
||||
|
||||
---
|
||||
|
||||
## ✅ ТЕКУЩИЙ СТАТУС
|
||||
|
||||
### Производительность:
|
||||
- ✅ **Открытие для редактирования:** ~1.5 секунды (было 25с)
|
||||
- ✅ **Просмотр:** ~0.2 секунды (прямые ссылки S3)
|
||||
- ✅ **CPU Nextcloud:** 0.05-5% (было 50%)
|
||||
- ✅ **Стабильность:** отлично
|
||||
|
||||
### Настройки:
|
||||
```php
|
||||
'memcache.distributed' => '\OC\Memcache\Redis', // ✅
|
||||
'filesystem_check_changes' => 1, // глобально
|
||||
```
|
||||
|
||||
```json
|
||||
// External Storage ID 1 (/crm)
|
||||
{
|
||||
"filesystem_check_changes": 0, // ✅ ОТКЛЮЧЕНО
|
||||
"encrypt": false, // ✅ ОТКЛЮЧЕНО
|
||||
"previews": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎉 РЕЗУЛЬТАТ
|
||||
|
||||
**Редактирование документов теперь открывается в 18 раз быстрее!**
|
||||
|
||||
- Было: 25 секунд ❌
|
||||
- Стало: 1.5 секунды ✅
|
||||
|
||||
**Это приемлемая скорость для редактирования!** 🚀
|
||||
|
||||
---
|
||||
|
||||
**Следующий шаг:** Настроить cron для периодического сканирования новых файлов
|
||||
|
||||
**Хочешь настрою cron?** 😊
|
||||
|
||||
|
||||
560
NEXTCLOUD_ЛАЙФХАКИ_УСКОРЕНИЯ.md
Normal file
560
NEXTCLOUD_ЛАЙФХАКИ_УСКОРЕНИЯ.md
Normal file
@@ -0,0 +1,560 @@
|
||||
# 🚀 NEXTCLOUD - ЛАЙФХАКИ УСКОРЕНИЯ
|
||||
|
||||
## 🎯 ЦЕЛЬ: Работать как Google Drive
|
||||
|
||||
---
|
||||
|
||||
## 💡 ПРЕДЛОЖЕНИЯ ДЛЯ ОБСУЖДЕНИЯ
|
||||
|
||||
### 1️⃣ **HTTP/2 Push и Preload** ⚡⚡⚡
|
||||
**Что:** Предзагрузка ресурсов до того как браузер их запросит
|
||||
|
||||
**Реализация в Nginx:**
|
||||
```nginx
|
||||
location ~ \.php$ {
|
||||
# Предзагрузка JS/CSS
|
||||
add_header Link "</core/css/styles.css>; rel=preload; as=style";
|
||||
add_header Link "</core/js/main.js>; rel=preload; as=script";
|
||||
|
||||
# HTTP/2 Push
|
||||
http2_push /core/css/styles.css;
|
||||
http2_push /core/js/main.js;
|
||||
}
|
||||
```
|
||||
|
||||
**Ожидаемый эффект:** +30-50% скорость загрузки интерфейса
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣ **Separating Data Directory на SSD** 💾⚡⚡⚡
|
||||
**Что:** Переместить БД Nextcloud на SSD диск (если сейчас на HDD)
|
||||
|
||||
**Проверка текущего диска:**
|
||||
```bash
|
||||
docker exec nextcloud-db-fresh df -h /var/lib/mysql
|
||||
lsblk
|
||||
```
|
||||
|
||||
**Если на HDD:**
|
||||
- Создать volume на SSD
|
||||
- Перенести `/var/lib/mysql` туда
|
||||
- **Ускорение:** 5-10x для случайных запросов
|
||||
|
||||
---
|
||||
|
||||
### 3️⃣ **Query Cache для MySQL** 🗄️⚡⚡
|
||||
**Что:** Кешировать результаты повторяющихся SQL запросов
|
||||
|
||||
**НО!** В MariaDB 10.6+ Query Cache УДАЛЁН (deprecated)
|
||||
|
||||
**Альтернатива - ProxySQL:**
|
||||
```bash
|
||||
docker run -d --name proxysql \
|
||||
--link nextcloud-db-fresh:mysql \
|
||||
-p 6033:6033 \
|
||||
proxysql/proxysql
|
||||
```
|
||||
|
||||
**Настройка:**
|
||||
- ProxySQL кеширует запросы к БД
|
||||
- Nextcloud подключается к ProxySQL вместо напрямую к MySQL
|
||||
- **Ускорение:** 2-3x для повторяющихся запросов
|
||||
|
||||
---
|
||||
|
||||
### 4️⃣ **Preview Pre-generation** 🖼️⚡⚡
|
||||
**Что:** Генерировать превью заранее в фоне
|
||||
|
||||
```bash
|
||||
# Установка приложения
|
||||
docker exec -u www-data nextcloud-fresh php occ app:install previewgenerator
|
||||
|
||||
# Настройка размеров
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:set previewgenerator squareSizes --value="32 256"
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:set previewgenerator widthSizes --value="256 384"
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:set previewgenerator heightSizes --value="256"
|
||||
|
||||
# Первичная генерация (один раз)
|
||||
docker exec -u www-data nextcloud-fresh php occ preview:generate-all
|
||||
|
||||
# Cron каждые 10 минут
|
||||
*/10 * * * * docker exec -u www-data nextcloud-fresh php occ preview:pre-generate
|
||||
```
|
||||
|
||||
**Эффект:** Превью файлов грузятся мгновенно
|
||||
|
||||
---
|
||||
|
||||
### 5️⃣ **Chunked Upload для больших файлов** 📤⚡⚡
|
||||
**Что:** Загрузка кусками вместо одного большого файла
|
||||
|
||||
```bash
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:set files max_chunk_size --value=10485760 # 10MB chunks
|
||||
```
|
||||
|
||||
**Для S3 External Storage:**
|
||||
```bash
|
||||
# Multipart upload для файлов > 5GB
|
||||
docker exec -u www-data nextcloud-fresh php occ config:system:set objectstore.s3.multipart_threshold --value=5368709120 --type=integer
|
||||
```
|
||||
|
||||
**Эффект:** Большие файлы загружаются быстрее и надёжнее
|
||||
|
||||
---
|
||||
|
||||
### 6️⃣ **MySQL InnoDB Optimization** 🗄️⚡⚡⚡
|
||||
**Что:** Правильные индексы для oc_filecache
|
||||
|
||||
**Текущие индексы:**
|
||||
```sql
|
||||
-- Проверка
|
||||
docker exec nextcloud-db-fresh mariadb -u root -proot_password nextcloud -e "SHOW INDEX FROM oc_filecache;" 2>&1 | grep -v insecure
|
||||
```
|
||||
|
||||
**Добавление недостающих:**
|
||||
```sql
|
||||
-- Для быстрого поиска по storage+path
|
||||
CREATE INDEX idx_storage_path_hash ON oc_filecache(storage, path_hash);
|
||||
|
||||
-- Для быстрого поиска дочерних файлов
|
||||
CREATE INDEX idx_parent_name ON oc_filecache(parent, name(191));
|
||||
|
||||
-- Для быстрого поиска по mimetype
|
||||
CREATE INDEX idx_storage_mimetype ON oc_filecache(storage, mimetype);
|
||||
|
||||
-- Для сортировки по времени
|
||||
CREATE INDEX idx_storage_mtime ON oc_filecache(storage, mtime);
|
||||
```
|
||||
|
||||
**Автоматически:**
|
||||
```bash
|
||||
docker exec -u www-data nextcloud-fresh php occ db:add-missing-indices
|
||||
docker exec -u www-data nextcloud-fresh php occ db:convert-filecache-bigint
|
||||
```
|
||||
|
||||
**Эффект:** +50-100% скорость запросов к файлам
|
||||
|
||||
---
|
||||
|
||||
### 7️⃣ **Nginx FastCGI Cache** 🌐⚡⚡⚡
|
||||
**Что:** Кеш статических ресурсов и некоторых динамических
|
||||
|
||||
**Конфигурация Nginx:**
|
||||
```nginx
|
||||
# В http блоке
|
||||
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=NEXTCLOUD:100m inactive=60m;
|
||||
fastcgi_cache_key "$scheme$request_method$host$request_uri";
|
||||
|
||||
server {
|
||||
location ~ ^\/(?:index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+)\.php(?:$|\/) {
|
||||
# FastCGI cache для избранных endpoints
|
||||
fastcgi_cache NEXTCLOUD;
|
||||
fastcgi_cache_valid 200 10m;
|
||||
fastcgi_cache_bypass $http_pragma $http_authorization;
|
||||
add_header X-Cache-Status $upstream_cache_status;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Эффект:** Статика грузится мгновенно
|
||||
|
||||
---
|
||||
|
||||
### 8️⃣ **Lazy Loading для больших папок** 📂⚡⚡
|
||||
**Что:** Не грузить все 24K файлов сразу, а порциями
|
||||
|
||||
**В Nextcloud уже есть!** Проверяем настройки:
|
||||
```bash
|
||||
# Pagination включена по умолчанию
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:get files pagination_limit
|
||||
# Если нет - ставим
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:set files pagination_limit --value=100
|
||||
```
|
||||
|
||||
**Эффект:** Папки открываются быстрее
|
||||
|
||||
---
|
||||
|
||||
### 9️⃣ **S3 Transfer Acceleration** 🌍⚡⚡⚡
|
||||
**Что:** Использовать CDN endpoints TWC Storage
|
||||
|
||||
**Проверяем у провайдера TWC:**
|
||||
- Есть ли CDN endpoint?
|
||||
- Есть ли региональные endpoints ближе к серверу?
|
||||
|
||||
**Если есть:**
|
||||
```php
|
||||
// В config.php Nextcloud для S3
|
||||
'objectstore' => [
|
||||
'arguments' => [
|
||||
'endpoint' => 'https://s3-accelerate.twcstorage.ru', // вместо обычного
|
||||
]
|
||||
]
|
||||
```
|
||||
|
||||
**Эффект:** +50-200% скорость доступа к S3
|
||||
|
||||
---
|
||||
|
||||
### 🔟 **WebDAV Keepalive Connections** 🔗⚡⚡
|
||||
**Что:** Переиспользовать HTTP соединения вместо создания новых
|
||||
|
||||
**В Nginx:**
|
||||
```nginx
|
||||
upstream nextcloud {
|
||||
server nextcloud-fresh:80;
|
||||
keepalive 32;
|
||||
}
|
||||
|
||||
server {
|
||||
location / {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
proxy_pass http://nextcloud;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Эффект:** Меньше латентности при множественных запросах
|
||||
|
||||
---
|
||||
|
||||
### 1️⃣1️⃣ **Collabora Caching** 📝⚡⚡⚡
|
||||
**Что:** Кеш документов и шрифтов в Collabora
|
||||
|
||||
**Проверка docker-compose Collabora:**
|
||||
```yaml
|
||||
collabora:
|
||||
environment:
|
||||
- DONT_GEN_SSL_CERT=true
|
||||
- extra_params=--o:ssl.enable=false --o:logging.level=warning --o:net.post_allow=crm\.clientright\.ru
|
||||
# Добавляем кеширование
|
||||
- extra_params=--o:admin_console.enable_pam=false --o:net.connection_timeout_secs=30 --o:per_document.idle_timeout_secs=3600
|
||||
# Добавляем volume для кеша
|
||||
volumes:
|
||||
- /var/cache/collabora:/tmp/cache
|
||||
```
|
||||
|
||||
**Создаём кеш папку:**
|
||||
```bash
|
||||
mkdir -p /var/cache/collabora
|
||||
chmod 777 /var/cache/collabora
|
||||
```
|
||||
|
||||
**Эффект:** Документы открываются в 2-3 раза быстрее
|
||||
|
||||
---
|
||||
|
||||
### 1️⃣2️⃣ **Browser Service Worker Cache** 🌐⚡⚡
|
||||
**Что:** Кеш в браузере через Service Workers
|
||||
|
||||
**Nextcloud уже использует!** Проверить можно:
|
||||
- F12 → Application → Service Workers
|
||||
- Должен быть зарегистрирован
|
||||
|
||||
**Если нет - включаем PWA:**
|
||||
```bash
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:set core enable_pwa --value=yes
|
||||
```
|
||||
|
||||
**Эффект:** Интерфейс грузится мгновенно после первого раза
|
||||
|
||||
---
|
||||
|
||||
### 1️⃣3️⃣ **Direct S3 Access (bypass Nextcloud)** 🔥⚡⚡⚡
|
||||
**Что:** Открывать файлы напрямую из S3, минуя Nextcloud WebDAV
|
||||
|
||||
**Реализация в CRM:**
|
||||
```php
|
||||
// В nextcloud-editor.js
|
||||
function openFileDirectFromS3(recordId) {
|
||||
// Получаем s3_key из CRM
|
||||
fetch(`/crm_extensions/nextcloud_api.php?record=${recordId}`)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
// Прямая ссылка на S3
|
||||
const s3Url = `https://s3.twcstorage.ru/${bucket}/${data.s3_key}`;
|
||||
|
||||
// Для просмотра - прямо открываем
|
||||
if (isViewMode) {
|
||||
window.open(s3Url, '_blank');
|
||||
}
|
||||
|
||||
// Для редактирования - через Nextcloud
|
||||
if (isEditMode) {
|
||||
openInCollabora(data.nc_path);
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**Эффект:**
|
||||
- Просмотр: мгновенно (прямо из S3)
|
||||
- Редактирование: через Nextcloud (нужен Collabora)
|
||||
|
||||
---
|
||||
|
||||
### 1️⃣4️⃣ **MySQL Connection Pooling** 🔗⚡⚡
|
||||
**Что:** Пул соединений к БД вместо открытия нового каждый раз
|
||||
|
||||
**ProxySQL (рекомендуется):**
|
||||
```yaml
|
||||
services:
|
||||
proxysql:
|
||||
image: proxysql/proxysql:latest
|
||||
ports:
|
||||
- "6033:6033"
|
||||
volumes:
|
||||
- ./proxysql.cnf:/etc/proxysql.cnf
|
||||
```
|
||||
|
||||
**Или в самом MySQL:**
|
||||
```sql
|
||||
SET GLOBAL max_connections = 500; -- увеличиваем пул
|
||||
SET GLOBAL thread_cache_size = 100; -- кеш потоков
|
||||
SET GLOBAL table_open_cache = 4096; -- кеш таблиц
|
||||
```
|
||||
|
||||
**Эффект:** Меньше overhead на создание соединений
|
||||
|
||||
---
|
||||
|
||||
### 1️⃣5️⃣ **Отключение Activity Log для CRM папки** 📊⚡⚡
|
||||
**Что:** Не логировать каждое действие в папке с 24K файлов
|
||||
|
||||
```bash
|
||||
# Отключаем activity для storage ID 4 (crm)
|
||||
docker exec nextcloud-db-fresh mariadb -u root -proot_password nextcloud -e "
|
||||
DELETE FROM oc_activity WHERE object_type = 'files' AND object_id IN (
|
||||
SELECT fileid FROM oc_filecache WHERE storage = 4
|
||||
);
|
||||
"
|
||||
|
||||
# Настройка в Nextcloud
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:set activity enable_activity_tracking --value=no
|
||||
```
|
||||
|
||||
**Эффект:** Меньше записей в БД при каждом открытии файла
|
||||
|
||||
---
|
||||
|
||||
### 1️⃣6️⃣ **CDN для статики Nextcloud** 🌍⚡⚡⚡
|
||||
**Что:** Статические файлы (JS/CSS/иконки) через CDN
|
||||
|
||||
**Cloudflare перед Nextcloud:**
|
||||
```nginx
|
||||
# В Nginx - доверяем Cloudflare IP
|
||||
set_real_ip_from 103.21.244.0/22;
|
||||
set_real_ip_from 103.22.200.0/22;
|
||||
# ... остальные Cloudflare IP
|
||||
real_ip_header CF-Connecting-IP;
|
||||
```
|
||||
|
||||
**В Nextcloud config.php:**
|
||||
```php
|
||||
'overwriteprotocol' => 'https',
|
||||
'trusted_proxies' => ['173.245.48.0/20', '103.21.244.0/22'],
|
||||
```
|
||||
|
||||
**Эффект:**
|
||||
- Статика грузится из ближайшего PoP
|
||||
- Меньше нагрузки на сервер
|
||||
- Быстрее для пользователей
|
||||
|
||||
---
|
||||
|
||||
### 1️⃣7️⃣ **Асинхронные операции через Jobs** ⏱️⚡⚡
|
||||
**Что:** Тяжёлые операции (сканирование, превью) в фоне
|
||||
|
||||
```bash
|
||||
# Cron вместо Ajax/Webcron
|
||||
docker exec -u www-data nextcloud-fresh php occ background:cron
|
||||
|
||||
# В host cron
|
||||
*/5 * * * * docker exec -u www-data nextcloud-fresh php occ cron:job:run
|
||||
```
|
||||
|
||||
**Важно для нас:**
|
||||
- Сканирование S3 - в фоне
|
||||
- Генерация превью - в фоне
|
||||
- Очистка кеша - в фоне
|
||||
|
||||
**Эффект:** UI не тормозит при открытии файлов
|
||||
|
||||
---
|
||||
|
||||
### 1️⃣8️⃣ **Partitioning таблицы oc_filecache** 🗂️⚡⚡⚡
|
||||
**Что:** Разбить огромную таблицу по storage ID
|
||||
|
||||
```sql
|
||||
-- Проверяем размер таблицы
|
||||
SELECT
|
||||
table_name AS `Table`,
|
||||
ROUND(((data_length + index_length) / 1024 / 1024), 2) AS `Size (MB)`
|
||||
FROM information_schema.TABLES
|
||||
WHERE table_schema = "nextcloud" AND table_name = "oc_filecache";
|
||||
|
||||
-- Если > 100MB - делаем partitioning
|
||||
ALTER TABLE oc_filecache
|
||||
PARTITION BY RANGE (storage) (
|
||||
PARTITION p_storage_1_3 VALUES LESS THAN (4),
|
||||
PARTITION p_storage_4 VALUES LESS THAN (5), -- CRM папка отдельно!
|
||||
PARTITION p_storage_other VALUES LESS THAN MAXVALUE
|
||||
);
|
||||
```
|
||||
|
||||
**Эффект:**
|
||||
- Запросы к CRM папке НЕ сканируют всю таблицу
|
||||
- +50-80% скорость для больших таблиц
|
||||
|
||||
---
|
||||
|
||||
### 1️⃣9️⃣ **Отдельный WOPI сервер для Collabora** 📝⚡⚡⚡
|
||||
**Что:** Collabora на отдельном сервере/контейнере
|
||||
|
||||
**Сейчас у вас:**
|
||||
- Nextcloud + Collabora на одном сервере
|
||||
- Конкурируют за ресурсы
|
||||
|
||||
**Оптимально:**
|
||||
```yaml
|
||||
# На другом сервере или с лимитами
|
||||
collabora:
|
||||
cpus: '2.0'
|
||||
mem_limit: 2g
|
||||
mem_reservation: 1g
|
||||
```
|
||||
|
||||
**Эффект:** Nextcloud и Collabora не мешают друг другу
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣0️⃣ **Varnish Cache перед Nextcloud** 🚀⚡⚡⚡
|
||||
**Что:** HTTP кеш для статики и некоторых API
|
||||
|
||||
```
|
||||
Browser → Varnish (6081) → Nginx → Nextcloud
|
||||
```
|
||||
|
||||
**VCL конфигурация:**
|
||||
```vcl
|
||||
# Кешируем статику
|
||||
if (req.url ~ "^/core/.*\.(js|css|png|jpg|svg)$") {
|
||||
return (hash); # кешируем
|
||||
}
|
||||
|
||||
# НЕ кешируем WebDAV
|
||||
if (req.url ~ "^/remote.php/dav/") {
|
||||
return (pass); # не кешируем
|
||||
}
|
||||
```
|
||||
|
||||
**Эффект:** Статика грузится в 10-100 раз быстрее
|
||||
|
||||
---
|
||||
|
||||
## 🎯 РЕКОМЕНДОВАННЫЙ ПЛАН
|
||||
|
||||
### **БЫСТРЫЕ ПОБЕДЫ (сделать сегодня):**
|
||||
|
||||
1. ✅ **Добавить индексы в MySQL** (5 минут)
|
||||
```bash
|
||||
docker exec -u www-data nextcloud-fresh php occ db:add-missing-indices
|
||||
```
|
||||
|
||||
2. ✅ **Preview Generator** (30 минут первый раз)
|
||||
```bash
|
||||
docker exec -u www-data nextcloud-fresh php occ app:install previewgenerator
|
||||
docker exec -u www-data nextcloud-fresh php occ preview:generate-all &
|
||||
```
|
||||
|
||||
3. ✅ **Отключить Activity для CRM папки** (2 минуты)
|
||||
```sql
|
||||
DELETE FROM oc_activity WHERE object_id IN (
|
||||
SELECT fileid FROM oc_filecache WHERE storage = 4
|
||||
);
|
||||
```
|
||||
|
||||
4. ✅ **Chunked upload** (1 минута)
|
||||
```bash
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:set files max_chunk_size --value=10485760
|
||||
```
|
||||
|
||||
**Ожидаемое ускорение:** +100-200%
|
||||
|
||||
---
|
||||
|
||||
### **СРЕДНЯЯ СЛОЖНОСТЬ (на неделе):**
|
||||
|
||||
5. ⬜ **InnoDB Buffer Pool 1GB** - требует изменение docker-compose
|
||||
6. ⬜ **Partitioning oc_filecache** - разделение таблицы
|
||||
7. ⬜ **HTTP/2 в Nginx** - если ещё не включено
|
||||
8. ⬜ **Cron для фоновых задач** - вместо webcron
|
||||
|
||||
**Ожидаемое ускорение:** +50-100%
|
||||
|
||||
---
|
||||
|
||||
### **ПРОДВИНУТОЕ (если нужно ещё быстрее):**
|
||||
|
||||
9. ⬜ **ProxySQL** - пул соединений к MySQL
|
||||
10. ⬜ **Varnish Cache** - HTTP кеш перед Nextcloud
|
||||
11. ⬜ **Отдельный сервер для Collabora** - разделение нагрузки
|
||||
12. ⬜ **CDN (Cloudflare)** - статика через CDN
|
||||
|
||||
**Ожидаемое ускорение:** +200-500%
|
||||
|
||||
---
|
||||
|
||||
### **ЭКСПЕРИМЕНТАЛЬНОЕ:**
|
||||
|
||||
13. ⬜ **Direct S3 access для просмотра** - обход Nextcloud
|
||||
14. ⬜ **Nginx caching для WebDAV** - кеш метаданных
|
||||
15. ⬜ **S3 Transfer Acceleration** - если TWC поддерживает
|
||||
|
||||
---
|
||||
|
||||
## 💰 СООТНОШЕНИЕ УСИЛИЙ/РЕЗУЛЬТАТА
|
||||
|
||||
| Решение | Сложность | Эффект | Приоритет |
|
||||
|---------|-----------|--------|-----------|
|
||||
| **Индексы MySQL** | ⭐ | ⭐⭐⭐ | 🔥🔥🔥 |
|
||||
| **Preview Generator** | ⭐ | ⭐⭐⭐ | 🔥🔥🔥 |
|
||||
| **Отключить Activity** | ⭐ | ⭐⭐ | 🔥🔥 |
|
||||
| **InnoDB Buffer 1GB** | ⭐⭐ | ⭐⭐⭐ | 🔥🔥🔥 |
|
||||
| **Partitioning** | ⭐⭐⭐ | ⭐⭐⭐ | 🔥🔥 |
|
||||
| **ProxySQL** | ⭐⭐⭐ | ⭐⭐ | 🔥 |
|
||||
| **Varnish** | ⭐⭐⭐⭐ | ⭐⭐⭐ | 🔥 |
|
||||
| **Direct S3** | ⭐⭐ | ⭐⭐⭐⭐ | 🔥🔥 |
|
||||
|
||||
---
|
||||
|
||||
## 🤔 МОИ РЕКОМЕНДАЦИИ
|
||||
|
||||
### **Начни с TOP-4:**
|
||||
|
||||
1. **Индексы MySQL** - 5 минут, +50% скорость
|
||||
2. **Preview Generator** - 30 минут, мгновенные превью
|
||||
3. **Отключить Activity для CRM** - 2 минуты, меньше записей в БД
|
||||
4. **InnoDB Buffer 1GB** - 10 минут, +30% скорость БД
|
||||
|
||||
**Это даст примерно 2-3x ускорение БЕЗ риска!**
|
||||
|
||||
---
|
||||
|
||||
### **Потом, если нужно ещё:**
|
||||
|
||||
5. **Partitioning oc_filecache** - разделить таблицу (сложнее, но эффективно)
|
||||
6. **Direct S3 access** - для просмотра файлов (очень быстро!)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 ВОПРОСЫ ДЛЯ ОБСУЖДЕНИЯ:
|
||||
|
||||
1. **Хочешь начать с быстрых побед** (индексы, preview, activity)?
|
||||
2. **Готов менять docker-compose** для InnoDB Buffer?
|
||||
3. **Интересует Direct S3 access** для просмотра файлов (обход Nextcloud)?
|
||||
4. **Рассматриваешь Varnish/ProxySQL** для серьёзного ускорения?
|
||||
|
||||
**Что выберем?** 😊
|
||||
|
||||
249
NEXTCLOUD_ОПТИМИЗАЦИЯ_31_10_2025.md
Normal file
249
NEXTCLOUD_ОПТИМИЗАЦИЯ_31_10_2025.md
Normal file
@@ -0,0 +1,249 @@
|
||||
# 🚀 ОПТИМИЗАЦИЯ NEXTCLOUD - 31 ОКТЯБРЯ 2025
|
||||
|
||||
## 🔍 ПРОБЛЕМА
|
||||
|
||||
### Симптомы:
|
||||
- ⏱️ Медленное открытие документов из CRM
|
||||
- 🔥 Nextcloud CPU: **50.43%** (постоянно)
|
||||
- ❌ Database Deadlocks при работе с файлами
|
||||
- 📊 24,000+ файлов в папке crm2
|
||||
|
||||
### Найденные проблемы:
|
||||
|
||||
#### 1. **Database Deadlocks** ❌
|
||||
```
|
||||
SQLSTATE[40001]: Serialization failure: 1213
|
||||
Deadlock found when trying to get lock; try restarting transaction
|
||||
```
|
||||
- Nextcloud постоянно сканирует папку crm2 (24K файлов)
|
||||
- Обновления таблицы `oc_filecache` создают блокировки
|
||||
- 404 блокировки (Innodb_row_lock_waits)
|
||||
|
||||
#### 2. **Отсутствие Distributed Cache** ❌
|
||||
- Был только `memcache.local` (APCu)
|
||||
- Не было `memcache.distributed` (Redis)
|
||||
- Кеш не переживал перезапуски
|
||||
|
||||
#### 3. **Автоматическое сканирование** ❌
|
||||
- `filesystem_check_changes = 1` (по умолчанию)
|
||||
- При каждом запросе Nextcloud сканировал все 24K файлов
|
||||
- Огромная нагрузка на БД
|
||||
|
||||
#### 4. **Маленький InnoDB Buffer Pool** ❌
|
||||
- Всего **128 MB** (должно быть минимум 512MB)
|
||||
- Не хватает памяти для кеширования запросов
|
||||
|
||||
---
|
||||
|
||||
## ✅ РЕШЕНИЕ
|
||||
|
||||
### 1. Distributed Cache (Redis)
|
||||
```bash
|
||||
docker exec -u www-data nextcloud-fresh php occ config:system:set memcache.distributed --value='\OC\Memcache\Redis'
|
||||
```
|
||||
|
||||
**Результат:**
|
||||
- ✅ Кеш между запросами
|
||||
- ✅ Меньше обращений к БД
|
||||
- ✅ Быстрее открытие файлов
|
||||
|
||||
### 2. Отключение автосканирования
|
||||
```bash
|
||||
docker exec -u www-data nextcloud-fresh php occ config:system:set filesystem_check_changes --value=0 --type=integer
|
||||
```
|
||||
|
||||
**Результат:**
|
||||
- ✅ Nextcloud НЕ сканирует 24K файлов при каждом запросе
|
||||
- ✅ Меньше Deadlocks
|
||||
- ✅ Меньше нагрузки на БД
|
||||
|
||||
**Важно:** Файлы будут обновляться через:
|
||||
- Redis события (`crm:file:events`)
|
||||
- Ручное сканирование при необходимости
|
||||
- WebDAV уведомления
|
||||
|
||||
### 3. Оптимизация PHP
|
||||
```bash
|
||||
# Увеличен memory_limit
|
||||
echo 'memory_limit=1024M' >> /usr/local/etc/php/conf.d/nextcloud.ini
|
||||
|
||||
# Оптимизация OPcache
|
||||
opcache.interned_strings_buffer=16
|
||||
opcache.max_accelerated_files=20000
|
||||
opcache.memory_consumption=256
|
||||
opcache.revalidate_freq=60
|
||||
```
|
||||
|
||||
**Результат:**
|
||||
- ✅ Больше памяти для PHP скриптов
|
||||
- ✅ Больше файлов в OPcache
|
||||
- ✅ Меньше компиляций
|
||||
|
||||
### 4. Оптимизация MySQL
|
||||
```sql
|
||||
SET GLOBAL max_connections = 300; -- было 151
|
||||
SET GLOBAL innodb_lock_wait_timeout = 120; -- было 50
|
||||
```
|
||||
|
||||
**Примечание:** `innodb_buffer_pool_size` требует изменения в docker-compose и перезапуск.
|
||||
|
||||
---
|
||||
|
||||
## 📊 РЕЗУЛЬТАТ
|
||||
|
||||
### До оптимизации:
|
||||
```
|
||||
CPU: 50.43% ❌
|
||||
Memory: 1005 MiB
|
||||
Deadlocks: много
|
||||
Скорость: медленно
|
||||
```
|
||||
|
||||
### После оптимизации:
|
||||
```
|
||||
CPU: 5.29% ✅ (в 10 раз меньше!)
|
||||
Memory: 645 MiB
|
||||
Deadlocks: должны уменьшиться
|
||||
Скорость: быстрее
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 ДОПОЛНИТЕЛЬНЫЕ РЕКОМЕНДАЦИИ
|
||||
|
||||
### 1. Увеличить InnoDB Buffer Pool (требует перезапуск)
|
||||
|
||||
Найти docker-compose файл Nextcloud и добавить:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
db:
|
||||
environment:
|
||||
MYSQL_INNODB_BUFFER_POOL_SIZE: 1073741824 # 1GB
|
||||
command:
|
||||
- --innodb-buffer-pool-size=1073741824
|
||||
- --innodb-log-file-size=268435456 # 256MB
|
||||
- --innodb-flush-log-at-trx_commit=2
|
||||
- --innodb-flush-method=O_DIRECT
|
||||
```
|
||||
|
||||
**Ожидаемое ускорение:** +30-50%
|
||||
|
||||
### 2. Добавить индексы в oc_filecache
|
||||
|
||||
```sql
|
||||
-- Проверить существующие индексы
|
||||
SHOW INDEX FROM oc_filecache;
|
||||
|
||||
-- Добавить если нужно
|
||||
CREATE INDEX idx_storage_path ON oc_filecache(storage, path(255));
|
||||
CREATE INDEX idx_parent_name ON oc_filecache(parent, name(255));
|
||||
```
|
||||
|
||||
**Ожидаемое ускорение:** +20-40%
|
||||
|
||||
### 3. Настроить Chunked Upload для больших файлов
|
||||
|
||||
```bash
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:set files max_chunk_size --value=10485760 # 10MB
|
||||
```
|
||||
|
||||
### 4. Включить Preview Generation в фоне
|
||||
|
||||
```bash
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:set previewgenerator squareSizes --value="256 512"
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:set previewgenerator widthSizes --value="256 512 1024"
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:set previewgenerator heightSizes --value="256 512 1024"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 МОНИТОРИНГ
|
||||
|
||||
### Проверка производительности:
|
||||
|
||||
```bash
|
||||
# CPU/Memory Nextcloud
|
||||
docker stats nextcloud-fresh --no-stream
|
||||
|
||||
# Deadlocks MySQL
|
||||
docker exec nextcloud-db-fresh mariadb -u root -proot_password -e "SHOW GLOBAL STATUS LIKE 'Innodb_row_lock%';" 2>&1 | grep -v insecure
|
||||
|
||||
# Redis статистика
|
||||
redis-cli -h 127.0.0.1 -p 6380 -a 'Nextcloud_Redis_Pass_2025!' INFO stats 2>&1 | grep -v Warning | head -10
|
||||
```
|
||||
|
||||
### Логи Nextcloud:
|
||||
```bash
|
||||
docker logs nextcloud-fresh --tail 50 | grep -i "error\|warning\|deadlock"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ ТЕКУЩИЕ НАСТРОЙКИ
|
||||
|
||||
### Nextcloud Config:
|
||||
```php
|
||||
'memcache.local' => '\OC\Memcache\APCu', // Локальный кеш
|
||||
'memcache.distributed' => '\OC\Memcache\Redis', // Распределённый кеш ✅ НОВОЕ
|
||||
'memcache.locking' => '\OC\Memcache\Redis', // Блокировки через Redis
|
||||
'filesystem_check_changes' => 0, // Автоскан ОТКЛЮЧЕН ✅ НОВОЕ
|
||||
```
|
||||
|
||||
### PHP:
|
||||
```
|
||||
memory_limit = 1024M (было 512M)
|
||||
opcache.memory_consumption = 256
|
||||
opcache.max_accelerated_files = 20000
|
||||
opcache.interned_strings_buffer = 16
|
||||
```
|
||||
|
||||
### MySQL:
|
||||
```
|
||||
max_connections = 300 (было 151)
|
||||
innodb_lock_wait_timeout = 120 (было 50)
|
||||
innodb_buffer_pool_size = 128MB (⚠️ НУЖНО 1GB)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 СЛЕДУЮЩИЕ ШАГИ
|
||||
|
||||
### Критично (сделать сегодня):
|
||||
1. ✅ Distributed cache - **ГОТОВО**
|
||||
2. ✅ Отключить автоскан - **ГОТОВО**
|
||||
3. ✅ PHP оптимизация - **ГОТОВО**
|
||||
4. ⏳ Проверить результат - **тестируем**
|
||||
|
||||
### Важно (сделать на неделе):
|
||||
1. ⬜ Увеличить InnoDB buffer pool до 1GB
|
||||
2. ⬜ Добавить индексы в oc_filecache
|
||||
3. ⬜ Настроить chunked upload
|
||||
4. ⬜ Preview generation в фоне
|
||||
|
||||
### Можно (если будет время):
|
||||
1. ⬜ APCu увеличить (apc.shm_size=128M)
|
||||
2. ⬜ Отключить ненужные приложения Nextcloud
|
||||
3. ⬜ Настроить логи на rotation
|
||||
4. ⬜ Мониторинг производительности
|
||||
|
||||
---
|
||||
|
||||
## 🎯 ОЖИДАЕМЫЙ РЕЗУЛЬТАТ
|
||||
|
||||
**После всех оптимизаций:**
|
||||
- ⚡ Открытие документов: **в 3-5 раз быстрее**
|
||||
- 📉 CPU Nextcloud: **< 10%** (было 50%)
|
||||
- ❌ Deadlocks: **минимум** (было много)
|
||||
- 💾 Меньше нагрузки на БД
|
||||
|
||||
---
|
||||
|
||||
**Статус:** ✅ Критичные оптимизации применены
|
||||
**CPU до:** 50.43%
|
||||
**CPU после:** 5.29%
|
||||
**Ускорение:** **~10x** 🚀
|
||||
|
||||
**Дата:** 31 октября 2025, 10:04
|
||||
**Автор:** Фёдор + AI Assistant
|
||||
|
||||
305
ONLYOFFICE_NEXTCLOUD_ИНТЕГРАЦИЯ.md
Normal file
305
ONLYOFFICE_NEXTCLOUD_ИНТЕГРАЦИЯ.md
Normal file
@@ -0,0 +1,305 @@
|
||||
# OnlyOffice + Nextcloud интеграция
|
||||
|
||||
**Дата:** 1 ноября 2025
|
||||
**Цель:** Использовать OnlyOffice Document Server для редактирования файлов в Nextcloud
|
||||
|
||||
---
|
||||
|
||||
## ✅ Что сделано
|
||||
|
||||
### 1. Установлено приложение ONLYOFFICE
|
||||
```bash
|
||||
docker exec -u www-data nextcloud-fresh php occ app:install onlyoffice
|
||||
# onlyoffice 9.11.0 installed ✅
|
||||
```
|
||||
|
||||
### 2. Настроен Document Server
|
||||
```bash
|
||||
# Адрес OnlyOffice Document Server
|
||||
DocumentServerUrl: https://office.clientright.ru:9443
|
||||
|
||||
# SSL проверка отключена
|
||||
verify_peer_off: true
|
||||
|
||||
# JWT отключен (как в OnlyOffice)
|
||||
jwt_secret: (пусто)
|
||||
jwt_header: (пусто)
|
||||
```
|
||||
|
||||
### 3. Отключен Collabora
|
||||
```bash
|
||||
# Чтобы не конфликтовал с OnlyOffice
|
||||
docker exec -u www-data nextcloud-fresh php occ app:disable richdocuments
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Теперь у тебя ДВА способа редактирования:
|
||||
|
||||
### Вариант 1: **Через CRM** (OnlyOffice напрямую)
|
||||
```
|
||||
CRM → open_file_v2.php → OnlyOffice (9443) → S3
|
||||
```
|
||||
**Преимущества:**
|
||||
- ⚡ **Молниеносно** (прямой доступ к S3)
|
||||
- ✅ Автосохранение каждые 5 сек
|
||||
- ✅ Файлы в CRM сразу обновляются
|
||||
|
||||
**Когда использовать:**
|
||||
- Работаешь в CRM
|
||||
- Нужна скорость
|
||||
- Редактируешь файлы клиентов
|
||||
|
||||
---
|
||||
|
||||
### Вариант 2: **Через Nextcloud** (OnlyOffice через Nextcloud)
|
||||
```
|
||||
Nextcloud → OnlyOffice (9443) → WebDAV → S3
|
||||
```
|
||||
**Преимущества:**
|
||||
- ✅ **Версионирование** файлов
|
||||
- ✅ История изменений
|
||||
- ✅ Права доступа через Nextcloud
|
||||
- ✅ Интеграция с Nextcloud UI
|
||||
|
||||
**Когда использовать:**
|
||||
- Работаешь в Nextcloud
|
||||
- Нужна история версий
|
||||
- Совместное редактирование
|
||||
- Управление файлами
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Как использовать в Nextcloud
|
||||
|
||||
### Шаг 1: Открой Nextcloud
|
||||
```
|
||||
https://office.clientright.ru:8443
|
||||
Логин: admin
|
||||
Пароль: office
|
||||
```
|
||||
|
||||
### Шаг 2: Перейди к файлам
|
||||
```
|
||||
Файлы → crm/crm2/CRM_Active_Files/Documents
|
||||
```
|
||||
|
||||
### Шаг 3: Открой файл для редактирования
|
||||
```
|
||||
Клик на файл .docx/.xlsx/.pptx
|
||||
→ Автоматически откроется OnlyOffice!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Сравнение OnlyOffice vs Collabora
|
||||
|
||||
| Характеристика | OnlyOffice | Collabora |
|
||||
|----------------|------------|-----------|
|
||||
| **Скорость** | ⚡⚡⚡ Молниеносно | 🐌 Медленнее |
|
||||
| **Форматы** | .docx, .xlsx, .pptx | .odt, .ods, .odp + docx |
|
||||
| **Совместимость** | MS Office | LibreOffice |
|
||||
| **Интерфейс** | Современный | Классический |
|
||||
| **Настройка** | Проще | Сложнее |
|
||||
| **Автосохранение** | ✅ Есть | ✅ Есть |
|
||||
| **Версионирование** | ✅ Через Nextcloud | ✅ Через Nextcloud |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Настройки OnlyOffice в Nextcloud
|
||||
|
||||
### Где найти:
|
||||
```
|
||||
Nextcloud → Настройки → Администрирование → ONLYOFFICE
|
||||
```
|
||||
|
||||
### Текущие настройки:
|
||||
```
|
||||
Document Server Address: https://office.clientright.ru:9443
|
||||
Secret key (JWT): (отключен)
|
||||
Verify peer off: ✅ (SSL проверка отключена)
|
||||
```
|
||||
|
||||
### Что можно настроить:
|
||||
- Форматы файлов для открытия
|
||||
- Права доступа (просмотр/редактирование)
|
||||
- Автосохранение (интервал)
|
||||
- Водяные знаки
|
||||
- Шаблоны документов
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Создание новых документов в Nextcloud
|
||||
|
||||
### Теперь можно создавать файлы прямо в Nextcloud!
|
||||
|
||||
**Как:**
|
||||
1. Открой Nextcloud Files
|
||||
2. Нажми **"+"** (New)
|
||||
3. Выбери:
|
||||
- 📄 **Document** (Word) → создаст .docx
|
||||
- 📊 **Spreadsheet** (Excel) → создаст .xlsx
|
||||
- 📽️ **Presentation** (PowerPoint) → создаст .pptx
|
||||
4. Файл откроется в OnlyOffice для редактирования!
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Команды для управления
|
||||
|
||||
### Просмотр настроек:
|
||||
```bash
|
||||
# Адрес Document Server
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:get onlyoffice DocumentServerUrl
|
||||
|
||||
# Проверка SSL
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:get onlyoffice verify_peer_off
|
||||
|
||||
# JWT секрет
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:get onlyoffice jwt_secret
|
||||
```
|
||||
|
||||
### Изменение настроек:
|
||||
```bash
|
||||
# Изменить адрес Document Server
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:set onlyoffice DocumentServerUrl --value="https://NEW_ADDRESS"
|
||||
|
||||
# Включить/отключить SSL проверку
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:set onlyoffice verify_peer_off --value="true"
|
||||
```
|
||||
|
||||
### Переключение между OnlyOffice и Collabora:
|
||||
```bash
|
||||
# Включить OnlyOffice
|
||||
docker exec -u www-data nextcloud-fresh php occ app:enable onlyoffice
|
||||
docker exec -u www-data nextcloud-fresh php occ app:disable richdocuments
|
||||
|
||||
# Включить Collabora
|
||||
docker exec -u www-data nextcloud-fresh php occ app:disable onlyoffice
|
||||
docker exec -u www-data nextcloud-fresh php occ app:enable richdocuments
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Отладка
|
||||
|
||||
### Если файл не открывается:
|
||||
|
||||
**1. Проверь доступность OnlyOffice:**
|
||||
```bash
|
||||
curl -s https://office.clientright.ru:9443/healthcheck
|
||||
# Должен вернуть: true
|
||||
```
|
||||
|
||||
**2. Проверь настройки в Nextcloud:**
|
||||
```bash
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:get onlyoffice DocumentServerUrl
|
||||
# Должно быть: https://office.clientright.ru:9443
|
||||
```
|
||||
|
||||
**3. Проверь логи OnlyOffice:**
|
||||
```bash
|
||||
docker logs --tail 50 onlyoffice-standalone
|
||||
```
|
||||
|
||||
**4. Проверь логи Nextcloud:**
|
||||
```bash
|
||||
docker exec -u www-data nextcloud-fresh php occ log:tail 50
|
||||
```
|
||||
|
||||
**5. Проверь что приложение включено:**
|
||||
```bash
|
||||
docker exec -u www-data nextcloud-fresh php occ app:list | grep onlyoffice
|
||||
# Должно быть в Enabled
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Безопасность
|
||||
|
||||
### ⚠️ Сейчас JWT отключен!
|
||||
|
||||
**Что это значит:**
|
||||
- Любой может использовать твой OnlyOffice сервер
|
||||
- Не рекомендуется для продакшна
|
||||
|
||||
**Для продакшна:**
|
||||
|
||||
**1. Включи JWT в OnlyOffice:**
|
||||
```bash
|
||||
# Генерируй случайный секрет
|
||||
SECRET=$(openssl rand -base64 32)
|
||||
|
||||
# Добавь в OnlyOffice config
|
||||
docker exec onlyoffice-standalone bash -c "cat > /etc/onlyoffice/documentserver/local-jwt.json << EOF
|
||||
{
|
||||
\"services\": {
|
||||
\"CoAuthoring\": {
|
||||
\"token\": {
|
||||
\"enable\": {
|
||||
\"request\": {
|
||||
\"inbox\": true,
|
||||
\"outbox\": true
|
||||
}
|
||||
}
|
||||
},
|
||||
\"secret\": {
|
||||
\"inbox\": {
|
||||
\"string\": \"$SECRET\"
|
||||
},
|
||||
\"outbox\": {
|
||||
\"string\": \"$SECRET\"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
"
|
||||
|
||||
# Перезапусти OnlyOffice
|
||||
docker restart onlyoffice-standalone
|
||||
```
|
||||
|
||||
**2. Добавь секрет в Nextcloud:**
|
||||
```bash
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:set onlyoffice jwt_secret --value="$SECRET"
|
||||
docker exec -u www-data nextcloud-fresh php occ config:app:set onlyoffice jwt_header --value="Authorization"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 Статистика
|
||||
|
||||
### Текущая конфигурация:
|
||||
- ✅ OnlyOffice Document Server: **9.0.4**
|
||||
- ✅ Nextcloud ONLYOFFICE app: **9.11.0**
|
||||
- ✅ Nextcloud: **30.0.2**
|
||||
- ✅ SSL: самоподписанный сертификат (проверка отключена)
|
||||
- ⚠️ JWT: **отключен** (включи для продакшна!)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Итог
|
||||
|
||||
### Что работает сейчас:
|
||||
1. ✅ **CRM → OnlyOffice** - прямой доступ (молниеносно)
|
||||
2. ✅ **Nextcloud → OnlyOffice** - через интеграцию (с версионированием)
|
||||
3. ✅ **Создание новых файлов** в Nextcloud
|
||||
4. ✅ **Редактирование** docx, xlsx, pptx
|
||||
5. ✅ **Автосохранение** в обоих случаях
|
||||
|
||||
### Что использовать:
|
||||
- **Работа в CRM** → используй прямой OnlyOffice (быстрее)
|
||||
- **Работа в Nextcloud** → используй интеграцию (больше функций)
|
||||
- **Создание новых файлов** → Nextcloud (удобнее)
|
||||
|
||||
---
|
||||
|
||||
**Попробуй открыть файл в Nextcloud! Должно работать!** 🚀
|
||||
|
||||
**Теперь у тебя:**
|
||||
- ⚡ Быстрое редактирование через CRM
|
||||
- 📚 Версионирование через Nextcloud
|
||||
- 🆕 Создание новых файлов
|
||||
- ✅ Единый OnlyOffice для всего!
|
||||
|
||||
271
ONLYOFFICE_STANDALONE_SETUP.md
Normal file
271
ONLYOFFICE_STANDALONE_SETUP.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# OnlyOffice Standalone — Настройка и Интеграция с S3
|
||||
|
||||
**Дата:** 31 октября 2025
|
||||
**Статус:** ✅ **РАБОТАЕТ! Файлы открываются!**
|
||||
⚠️ **TODO:** Доработать сохранение обратно в оригинальный путь S3
|
||||
|
||||
---
|
||||
|
||||
## 🎯 ЧТО СДЕЛАНО
|
||||
|
||||
### 1. Поднят новый Standalone OnlyOffice Document Server
|
||||
|
||||
**Контейнер:** `onlyoffice-standalone`
|
||||
**Порт:** `127.0.0.1:8083` → проксируется через Nginx на `office.clientright.ru:9443`
|
||||
**Docker Compose:** `/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/file_storage/docker-compose.onlyoffice-standalone.yml`
|
||||
|
||||
**Конфигурация:**
|
||||
- JWT отключён (`JWT_ENABLED=false`)
|
||||
- WOPI отключён (`WOPI_ENABLED=false`)
|
||||
- Полностью независим от Nextcloud
|
||||
- Работает в standalone режиме
|
||||
|
||||
### 2. Настроен Nginx
|
||||
|
||||
**Файл:** `/etc/nginx/conf.d/01-onlyoffice-standalone.conf`
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 147.45.146.17:9443 ssl http2;
|
||||
server_name onlyoffice.clientright.ru office.clientright.ru;
|
||||
|
||||
# SSL от office.clientright.ru (используем тот же сертификат)
|
||||
ssl_certificate /etc/letsencrypt/live/office.clientright.ru/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/office.clientright.ru/privkey.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8083;
|
||||
# ВАЖНО: Host с портом для правильной генерации URL кеша
|
||||
proxy_set_header Host $host:$server_port;
|
||||
proxy_set_header X-Forwarded-Host $host:$server_port;
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Доступ:** `https://office.clientright.ru:9443`
|
||||
|
||||
### 3. Создан open_file_v3_standalone.php
|
||||
|
||||
**Файл:** `crm_extensions/file_storage/api/open_file_v3_standalone.php`
|
||||
|
||||
**Особенности:**
|
||||
- ✅ Использует standalone OnlyOffice (порт 9443)
|
||||
- ✅ Прямой S3 URL (публичный bucket)
|
||||
- ✅ Уникальный `documentKey` при каждом запросе (обход кеша)
|
||||
- ✅ Сохраняет маппинг `documentKey → S3 path` в Redis
|
||||
- ✅ Поддержка Range requests из коробки (S3)
|
||||
- ✅ CORS настроен правильно
|
||||
|
||||
### 4. Обновлён onlyoffice_callback.php
|
||||
|
||||
**Файл:** `crm_extensions/file_storage/api/onlyoffice_callback.php`
|
||||
|
||||
**Что делает:**
|
||||
- ✅ Принимает callback от OnlyOffice при сохранении
|
||||
- ✅ Скачивает изменённый файл от OnlyOffice
|
||||
- ✅ Загружает в S3 (временно в папку `onlyoffice_saved/`)
|
||||
- ⚠️ **TODO:** Сохранять в оригинальный путь (используя Redis маппинг)
|
||||
|
||||
### 5. Обновлён open_file_v2.php
|
||||
|
||||
**Файл:** `crm_extensions/file_storage/api/open_file_v2.php`
|
||||
|
||||
**Изменения:**
|
||||
- ✅ Переключён на standalone OnlyOffice (9443)
|
||||
- ✅ Использует прямой S3 URL (без proxy)
|
||||
- ✅ Добавлена информация о пользователе (`user.id`, `user.name`)
|
||||
- ✅ Улучшен CSS для полноэкранного режима
|
||||
|
||||
### 6. Создан s3_proxy.php
|
||||
|
||||
**Файл:** `crm_extensions/file_storage/api/s3_proxy.php`
|
||||
|
||||
**Особенности:**
|
||||
- ✅ Поддержка HTTP Range requests (206 Partial Content)
|
||||
- ✅ Правильные CORS headers
|
||||
- ✅ Обработка HEAD requests
|
||||
- ✅ Проксирование файлов из S3
|
||||
|
||||
*(Сейчас не используется, так как S3 bucket публичный и OnlyOffice работает напрямую)*
|
||||
|
||||
---
|
||||
|
||||
## 🚀 КАК ЭТО РАБОТАЕТ
|
||||
|
||||
### Открытие файла
|
||||
|
||||
1. **CRM** → Кнопка "Редактировать" → `open_file_v2.php?recordId=X&fileName=...`
|
||||
2. **PHP скрипт:**
|
||||
- Извлекает S3 путь из URL
|
||||
- Генерирует уникальный `documentKey`
|
||||
- Сохраняет маппинг `documentKey → S3 path` в Redis (TTL 24ч)
|
||||
- Отдаёт HTML с OnlyOffice редактором
|
||||
3. **Браузер:**
|
||||
- Загружает OnlyOffice API с `office.clientright.ru:9443`
|
||||
- OnlyOffice скачивает файл **напрямую из S3** (публичный bucket)
|
||||
- Документ открывается в редакторе
|
||||
|
||||
### Сохранение файла
|
||||
|
||||
1. **Пользователь редактирует** → Автосохранение каждые несколько секунд
|
||||
2. **OnlyOffice** → `POST https://crm.clientright.ru/.../onlyoffice_callback.php`
|
||||
3. **Callback скрипт:**
|
||||
- Получает `status: 2` (файл сохранён)
|
||||
- Скачивает изменённый файл от OnlyOffice
|
||||
- **ВРЕМЕННО:** Сохраняет в `onlyoffice_saved/` в S3
|
||||
- **TODO:** Получить оригинальный путь из Redis и перезаписать файл там
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ TODO (НА ЗАВТРА)
|
||||
|
||||
### 1. Доработать сохранение в оригинальный путь
|
||||
|
||||
**Проблема:** Сейчас файлы сохраняются в `onlyoffice_saved/`, а не в оригинальный путь.
|
||||
|
||||
**Решение:**
|
||||
```php
|
||||
// В onlyoffice_callback.php:
|
||||
$redis = new Predis\Client([...]);
|
||||
$mapping = $redis->get("crm:onlyoffice:key:$documentKey");
|
||||
$data = json_decode($mapping, true);
|
||||
$originalPath = $data['s3_path'];
|
||||
|
||||
// Сохраняем в ОРИГИНАЛЬНЫЙ путь
|
||||
$s3Client->putObject([
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $originalPath, // ← СЮДА!
|
||||
'Body' => $fileContent
|
||||
]);
|
||||
```
|
||||
|
||||
### 2. Обновить CRM JS для использования нового скрипта
|
||||
|
||||
**Файл:** `layouts/v7/lib/nextcloud-editor.js`
|
||||
|
||||
Изменить вызов с `open_file_v2.php` на `open_file_v3_standalone.php`.
|
||||
|
||||
### 3. Тестирование
|
||||
|
||||
- [ ] Открытие разных типов файлов (docx, xlsx, pptx)
|
||||
- [ ] Редактирование и сохранение
|
||||
- [ ] Совместная работа (несколько пользователей)
|
||||
- [ ] Проверка что файлы сохраняются в правильный путь
|
||||
|
||||
### 4. Очистка
|
||||
|
||||
Удалить тестовые файлы:
|
||||
- `test_onlyoffice_simple.php`
|
||||
- `test_onlyoffice_direct.php`
|
||||
- `test_presigned.php`
|
||||
|
||||
---
|
||||
|
||||
## 📊 РЕЗУЛЬТАТ
|
||||
|
||||
### ✅ ЧТО РАБОТАЕТ
|
||||
|
||||
- **OnlyOffice Standalone запущен и работает**
|
||||
- **Файлы открываются БЫСТРО** (напрямую из S3)
|
||||
- **Редактор загружается полностью**
|
||||
- **Поддержка Range requests** (S3)
|
||||
- **CORS настроен правильно**
|
||||
- **Callback вызывается** при сохранении
|
||||
|
||||
### ⚠️ ЧТО НУЖНО ДОРАБОТАТЬ
|
||||
|
||||
- **Сохранение в оригинальный путь** (сейчас в `onlyoffice_saved/`)
|
||||
- **Тестирование разных файлов** (некоторые могут быть повреждены)
|
||||
- **Обновление CRM JS** для использования нового скрипта
|
||||
|
||||
---
|
||||
|
||||
## 🔧 ТЕХНИЧЕСКАЯ ИНФОРМАЦИЯ
|
||||
|
||||
### Порты
|
||||
|
||||
- **8081:** Старый OnlyOffice (через Nextcloud/WOPI) — `office.clientright.ru:8443`
|
||||
- **8083:** **Новый Standalone OnlyOffice** — `office.clientright.ru:9443`
|
||||
|
||||
### URLs
|
||||
|
||||
- **OnlyOffice API:** `https://office.clientright.ru:9443/web-apps/apps/api/documents/api.js`
|
||||
- **Открытие файла:** `https://crm.clientright.ru/crm_extensions/file_storage/api/open_file_v2.php`
|
||||
- **Callback:** `https://crm.clientright.ru/crm_extensions/file_storage/api/onlyoffice_callback.php`
|
||||
- **S3 Bucket:** `https://s3.twcstorage.ru/f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c/`
|
||||
|
||||
### Redis Keys
|
||||
|
||||
```
|
||||
crm:onlyoffice:key:{documentKey} → {"s3_path":"...", "record_id":"...", "created_at":...}
|
||||
TTL: 86400 секунд (24 часа)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 ИЗВЕСТНЫЕ ПРОБЛЕМЫ
|
||||
|
||||
### 1. Некоторые файлы показывают только toolbar
|
||||
|
||||
**Причина:** Документ может быть повреждён или пустой
|
||||
**Решение:** Проверить файл вручную, попробовать открыть другой
|
||||
|
||||
### 2. Pre-signed URL не работают для публичного bucket
|
||||
|
||||
**Причина:** S3 Timeweb возвращает 403 для pre-signed URL если bucket публичный
|
||||
**Решение:** Используем прямой S3 URL (bucket публичный, подпись не нужна)
|
||||
|
||||
### 3. Старый OnlyOffice (8443) использует Nextcloud кеш
|
||||
|
||||
**Причина:** OnlyOffice на порту 8443 интегрирован с Nextcloud через WOPI
|
||||
**Решение:** Используем новый standalone OnlyOffice (9443)
|
||||
|
||||
---
|
||||
|
||||
## 📝 КОМАНДЫ ДЛЯ УПРАВЛЕНИЯ
|
||||
|
||||
### Перезапуск OnlyOffice Standalone
|
||||
|
||||
```bash
|
||||
cd /var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/file_storage
|
||||
docker-compose -f docker-compose.onlyoffice-standalone.yml restart
|
||||
```
|
||||
|
||||
### Просмотр логов
|
||||
|
||||
```bash
|
||||
docker logs -f onlyoffice-standalone
|
||||
```
|
||||
|
||||
### Очистка кеша
|
||||
|
||||
```bash
|
||||
docker exec onlyoffice-standalone rm -rf /var/lib/onlyoffice/documentserver/App_Data/cache/files/*
|
||||
docker restart onlyoffice-standalone
|
||||
```
|
||||
|
||||
### Проверка Redis маппинга
|
||||
|
||||
```bash
|
||||
redis-cli -h crm.clientright.ru -p 6379 -a 'CRM_Redis_Pass_2025_Secure!' \
|
||||
KEYS 'crm:onlyoffice:key:*'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎓 ЧТО УЗНАЛИ
|
||||
|
||||
1. **OnlyOffice Community Edition** работает в standalone режиме без ограничений на открытие файлов
|
||||
2. **S3 публичный bucket** поддерживает Range requests и CORS из коробки
|
||||
3. **Pre-signed URL** не работают для публичных bucket на S3 Timeweb
|
||||
4. **OnlyOffice требует уникальный documentKey** для каждого открытия (иначе использует старый кеш)
|
||||
5. **Host header важен** — OnlyOffice генерирует URL для кеша на основе Host
|
||||
6. **Callback обязателен** для режима редактирования
|
||||
|
||||
---
|
||||
|
||||
**Автор:** AI Assistant
|
||||
**Дата:** 31.10.2025 23:40
|
||||
**Следующий шаг:** Доработать callback для сохранения в оригинальный путь S3
|
||||
|
||||
401
ONLYOFFICE_НАСТРОЙКИ.md
Normal file
401
ONLYOFFICE_НАСТРОЙКИ.md
Normal file
@@ -0,0 +1,401 @@
|
||||
# OnlyOffice Document Server - Настройки
|
||||
|
||||
**Важно:** У OnlyOffice Community Edition **НЕТ веб-интерфейса** для настроек!
|
||||
Все настраивается через **JSON конфиг файлы**.
|
||||
|
||||
---
|
||||
|
||||
## 📂 Конфигурационные файлы
|
||||
|
||||
### Расположение:
|
||||
```
|
||||
/etc/onlyoffice/documentserver/
|
||||
```
|
||||
|
||||
### Основные файлы:
|
||||
|
||||
1. **`default.json`** - базовые настройки по умолчанию (не трогать!)
|
||||
2. **`production-linux.json`** - настройки для production (автоматически)
|
||||
3. **`local.json`** - **локальные переопределения** (редактируем этот!)
|
||||
4. **`local-production-linux.json`** - дополнительные кастомные настройки
|
||||
|
||||
**Приоритет:** `local-*.json` > `local.json` > `production-*.json` > `default.json`
|
||||
|
||||
---
|
||||
|
||||
## 🎛️ Что можно настроить
|
||||
|
||||
### 1. **JWT Token (безопасность)**
|
||||
|
||||
**Файл:** `local.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"services": {
|
||||
"CoAuthoring": {
|
||||
"token": {
|
||||
"enable": {
|
||||
"request": {
|
||||
"inbox": false, ← Проверять JWT в запросах от браузера
|
||||
"outbox": false ← Добавлять JWT в callback запросы
|
||||
},
|
||||
"browser": false ← Требовать JWT от браузера
|
||||
}
|
||||
},
|
||||
"secret": {
|
||||
"inbox": {
|
||||
"string": "ВАШ_СЕКРЕТНЫЙ_КЛЮЧ" ← Секрет для JWT
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Сейчас:** JWT отключен (`false`) - любой может использовать!
|
||||
**Для продакшна:** Включить JWT (`true`) + установить секрет!
|
||||
|
||||
---
|
||||
|
||||
### 2. **SSL/TLS для callback**
|
||||
|
||||
**Файл:** `local-production-linux.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"services": {
|
||||
"CoAuthoring": {
|
||||
"utils": {
|
||||
"rejectUnauthorized": false ← Игнорировать SSL ошибки (у нас включено!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Зачем:** Если callback URL имеет самоподписанный сертификат.
|
||||
|
||||
---
|
||||
|
||||
### 3. **Фильтрация IP адресов**
|
||||
|
||||
**Файл:** `local-production-linux.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"services": {
|
||||
"CoAuthoring": {
|
||||
"request-filtering-agent": {
|
||||
"allowPrivateIPAddress": true, ← Разрешить приватные IP (у нас включено!)
|
||||
"allowMetaIPAddress": true ← Разрешить мета-адреса
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Зачем:** Чтобы OnlyOffice мог вызывать callback на локальные IP (147.45.146.17).
|
||||
|
||||
---
|
||||
|
||||
### 4. **База данных (PostgreSQL)**
|
||||
|
||||
**Файл:** `local.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"services": {
|
||||
"CoAuthoring": {
|
||||
"sql": {
|
||||
"type": "postgres",
|
||||
"dbHost": "localhost",
|
||||
"dbPort": "5432",
|
||||
"dbName": "onlyoffice",
|
||||
"dbUser": "onlyoffice",
|
||||
"dbPass": "onlyoffice"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Сейчас:** Используется встроенная PostgreSQL в контейнере.
|
||||
|
||||
---
|
||||
|
||||
### 5. **Лимиты и производительность**
|
||||
|
||||
```json
|
||||
{
|
||||
"services": {
|
||||
"CoAuthoring": {
|
||||
"server": {
|
||||
"port": 8000,
|
||||
"workersPerCpu": 1, ← Воркеров на CPU (сейчас: 1)
|
||||
"limits_tempfile_upload": 104857600, ← Макс размер загружаемого файла (100MB)
|
||||
"limits_image_size": 26214400, ← Макс размер изображения (25MB)
|
||||
"limits_image_download_timeout": 120000 ← Таймаут загрузки (2 мин)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. **WOPI (интеграция с Microsoft)**
|
||||
|
||||
**Файл:** `local.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"wopi": {
|
||||
"enable": false ← WOPI отключен (не нужен нам)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Зачем:** Для интеграции с Office Online, SharePoint и т.д.
|
||||
|
||||
---
|
||||
|
||||
### 7. **Логирование**
|
||||
|
||||
**Файл:** `/etc/onlyoffice/documentserver/log4js/production.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"appenders": {
|
||||
"file": {
|
||||
"type": "file",
|
||||
"filename": "/var/log/onlyoffice/documentserver/docservice/out.log",
|
||||
"maxLogSize": 10485760, ← Макс размер лога (10MB)
|
||||
"backups": 3 ← Количество бэкапов
|
||||
}
|
||||
},
|
||||
"levels": {
|
||||
"nodeJS": "WARN" ← Уровень логирования (DEBUG, INFO, WARN, ERROR)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Как редактировать настройки
|
||||
|
||||
### Вариант 1: Через `docker exec` (быстро)
|
||||
|
||||
```bash
|
||||
# Редактировать local.json
|
||||
docker exec -it onlyoffice-standalone nano /etc/onlyoffice/documentserver/local.json
|
||||
|
||||
# Перезапустить OnlyOffice
|
||||
docker restart onlyoffice-standalone
|
||||
```
|
||||
|
||||
### Вариант 2: Создать новый конфиг файл (рекомендуется)
|
||||
|
||||
```bash
|
||||
# Создать кастомный конфиг
|
||||
docker exec onlyoffice-standalone bash -c 'cat > /etc/onlyoffice/documentserver/local-production-linux.json << "EOF"
|
||||
{
|
||||
"services": {
|
||||
"CoAuthoring": {
|
||||
"server": {
|
||||
"workersPerCpu": 2 ← Увеличить воркеры
|
||||
},
|
||||
"utils": {
|
||||
"rejectUnauthorized": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
'
|
||||
|
||||
# Перезапустить
|
||||
docker restart onlyoffice-standalone
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Текущие настройки (у нас)
|
||||
|
||||
### `local.json`:
|
||||
- ✅ JWT **отключен** (`false`)
|
||||
- ✅ PostgreSQL на `localhost:5432`
|
||||
- ✅ RabbitMQ на `localhost`
|
||||
- ✅ WOPI **отключен**
|
||||
|
||||
### `local-production-linux.json`:
|
||||
- ✅ `rejectUnauthorized: false` - игнорируем SSL ошибки
|
||||
- ✅ `allowPrivateIPAddress: true` - разрешаем callback на 147.45.146.17
|
||||
- ✅ `allowMetaIPAddress: true`
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Полезные команды
|
||||
|
||||
### Просмотр текущих настроек:
|
||||
```bash
|
||||
# Основной конфиг
|
||||
docker exec onlyoffice-standalone cat /etc/onlyoffice/documentserver/local.json | python3 -m json.tool
|
||||
|
||||
# Наш кастомный конфиг
|
||||
docker exec onlyoffice-standalone cat /etc/onlyoffice/documentserver/local-production-linux.json | python3 -m json.tool
|
||||
|
||||
# Все конфиги
|
||||
docker exec onlyoffice-standalone find /etc/onlyoffice/documentserver -name "*.json" -type f
|
||||
```
|
||||
|
||||
### Редактирование:
|
||||
```bash
|
||||
# Редактировать через nano
|
||||
docker exec -it onlyoffice-standalone nano /etc/onlyoffice/documentserver/local.json
|
||||
|
||||
# Или через vi
|
||||
docker exec -it onlyoffice-standalone vi /etc/onlyoffice/documentserver/local.json
|
||||
```
|
||||
|
||||
### Проверка синтаксиса JSON:
|
||||
```bash
|
||||
docker exec onlyoffice-standalone cat /etc/onlyoffice/documentserver/local.json | python3 -m json.tool
|
||||
# Если ошибка - JSON невалидный!
|
||||
```
|
||||
|
||||
### Перезапуск после изменений:
|
||||
```bash
|
||||
docker restart onlyoffice-standalone
|
||||
sleep 20 # Подождать полного запуска
|
||||
docker logs --tail 20 onlyoffice-standalone
|
||||
```
|
||||
|
||||
### Просмотр логов:
|
||||
```bash
|
||||
# Логи Document Server
|
||||
docker exec onlyoffice-standalone tail -100 /var/log/onlyoffice/documentserver/docservice/out.log
|
||||
|
||||
# Логи ошибок
|
||||
docker exec onlyoffice-standalone tail -100 /var/log/onlyoffice/documentserver/docservice/err.log
|
||||
|
||||
# Логи nginx
|
||||
docker exec onlyoffice-standalone tail -100 /var/log/onlyoffice/documentserver/nginx.error.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Важные параметры для продакшна
|
||||
|
||||
### 1. Включить JWT (обязательно!)
|
||||
```json
|
||||
{
|
||||
"services": {
|
||||
"CoAuthoring": {
|
||||
"token": {
|
||||
"enable": {
|
||||
"request": {
|
||||
"inbox": true,
|
||||
"outbox": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"secret": {
|
||||
"inbox": {
|
||||
"string": "СГЕНЕРИРУЙ_СЛУЧАЙНЫЙ_КЛЮЧ_64_СИМВОЛА"
|
||||
},
|
||||
"outbox": {
|
||||
"string": "СГЕНЕРИРУЙ_СЛУЧАЙНЫЙ_КЛЮЧ_64_СИМВОЛА"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Увеличить воркеры (если много пользователей)
|
||||
```json
|
||||
{
|
||||
"services": {
|
||||
"CoAuthoring": {
|
||||
"server": {
|
||||
"workersPerCpu": 2 ← По умолчанию 1, можно 2-4
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Увеличить лимиты файлов
|
||||
```json
|
||||
{
|
||||
"services": {
|
||||
"CoAuthoring": {
|
||||
"server": {
|
||||
"limits_tempfile_upload": 209715200 ← 200MB вместо 100MB
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Официальная документация
|
||||
|
||||
**Ссылки:**
|
||||
- Конфигурация: https://api.onlyoffice.com/editors/config/
|
||||
- JWT: https://api.onlyoffice.com/editors/signature/
|
||||
- Docker: https://github.com/ONLYOFFICE/Docker-DocumentServer
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Мониторинг OnlyOffice
|
||||
|
||||
### Healthcheck:
|
||||
```bash
|
||||
curl https://office.clientright.ru:9443/healthcheck
|
||||
# Должен вернуть: true
|
||||
```
|
||||
|
||||
### Версия:
|
||||
```bash
|
||||
docker exec onlyoffice-standalone cat /var/www/onlyoffice/documentserver/server/DocService/package.json | grep version
|
||||
# Сейчас: 9.0.4
|
||||
```
|
||||
|
||||
### Статистика:
|
||||
```bash
|
||||
docker stats onlyoffice-standalone
|
||||
# Показывает: CPU, RAM, Network
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 Советы
|
||||
|
||||
1. **Всегда делай бэкап конфигов перед изменениями:**
|
||||
```bash
|
||||
docker exec onlyoffice-standalone cp /etc/onlyoffice/documentserver/local.json /etc/onlyoffice/documentserver/local.json.backup
|
||||
```
|
||||
|
||||
2. **Проверяй синтаксис JSON после редактирования:**
|
||||
```bash
|
||||
cat local.json | python3 -m json.tool
|
||||
```
|
||||
|
||||
3. **Читай логи после перезапуска:**
|
||||
```bash
|
||||
docker logs -f onlyoffice-standalone
|
||||
```
|
||||
|
||||
4. **Для продакшна - обязательно включи JWT!** Иначе кто угодно может использовать твой OnlyOffice сервер!
|
||||
|
||||
---
|
||||
|
||||
**Если нужен веб-интерфейс - смотри в сторону:**
|
||||
- **ONLYOFFICE Workspace** (платный, но с полной админ-панелью)
|
||||
- **Nextcloud + OnlyOffice** (есть UI в Nextcloud)
|
||||
- **ownCloud + OnlyOffice** (есть UI в ownCloud)
|
||||
|
||||
**У нас:** Standalone Document Server (нет UI, но работает быстро!)
|
||||
|
||||
191
WORKFLOW_OCR_КАК_РАБОТАЕТ.md
Normal file
191
WORKFLOW_OCR_КАК_РАБОТАЕТ.md
Normal file
@@ -0,0 +1,191 @@
|
||||
# 🔍 КАК РАБОТАЕТ WORKFLOW OCR
|
||||
|
||||
## ⚠️ **ВАЖНО: Нет ручного запуска!**
|
||||
|
||||
**Workflow OCR** работает **ТОЛЬКО через правила автоматизации**!
|
||||
|
||||
В нём **НЕТ** кнопки "Submit to OCR" в меню файла.
|
||||
|
||||
---
|
||||
|
||||
## 🔄 **КАК ЭТО РАБОТАЕТ:**
|
||||
|
||||
### **Принцип работы:**
|
||||
```
|
||||
1. Создаёшь правило workflow
|
||||
2. Загружаешь файл в Nextcloud
|
||||
3. Workflow Engine видит событие
|
||||
4. Запускает OCR автоматически
|
||||
5. Результат сохраняется
|
||||
```
|
||||
|
||||
**Это АВТОМАТИЗАЦИЯ, а не ручной инструмент!**
|
||||
|
||||
---
|
||||
|
||||
## 📝 **КАК СОЗДАТЬ ПРАВИЛО:**
|
||||
|
||||
### **Вариант 1: Через интерфейс Nextcloud**
|
||||
|
||||
**Шаг 1: Открой настройки Flow**
|
||||
```
|
||||
https://office.clientright.ru:8443/settings/admin/workflow
|
||||
```
|
||||
|
||||
**Шаг 2: Добавь правило**
|
||||
1. Нажми **"Add new workflow"**
|
||||
2. Выбери условия:
|
||||
- **When:** File created
|
||||
- **And:** File MIME type is `application/pdf`
|
||||
- **And:** Path matches `Documents/Project/*`
|
||||
3. Выбери действие:
|
||||
- **Then:** `OCR processing`
|
||||
4. Нажми **Save**
|
||||
|
||||
---
|
||||
|
||||
### **Вариант 2: Через команду (автоматически)**
|
||||
|
||||
Я могу создать правило автоматически через БД!
|
||||
|
||||
Команда создаст правило:
|
||||
```
|
||||
Имя: "CRM - Автоматический OCR судебных документов"
|
||||
|
||||
Условия:
|
||||
- Файл создан
|
||||
- Тип: PDF
|
||||
- Имя содержит: решение, определение, постановление, договор, иск
|
||||
|
||||
Действие:
|
||||
- OCR с русским + английским
|
||||
- Создать searchable PDF
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 **КАК ПРОТЕСТИРОВАТЬ:**
|
||||
|
||||
### **После создания правила:**
|
||||
|
||||
1. **Загрузи тестовый PDF** в папку проекта
|
||||
2. **Дождись 30-60 секунд** (OCR работает в фоне)
|
||||
3. **Проверь через поиск:**
|
||||
```
|
||||
Files → Search (🔍)
|
||||
Введи слово из документа
|
||||
```
|
||||
4. Если находит → **OCR работает!** ✅
|
||||
|
||||
---
|
||||
|
||||
## 📊 **КАК ПРОВЕРИТЬ ЧТО OCR ВЫПОЛНЕН:**
|
||||
|
||||
### **Способ 1: Через теги**
|
||||
После OCR файл получит тег в Nextcloud (если настроено)
|
||||
|
||||
### **Способ 2: Через логи**
|
||||
```bash
|
||||
docker exec nextcloud-fresh tail -100 /var/www/html/data/nextcloud.log | grep -i ocr
|
||||
```
|
||||
|
||||
### **Способ 3: Через поиск**
|
||||
Если файл находится по тексту из него → OCR выполнен!
|
||||
|
||||
---
|
||||
|
||||
## ⚡ **АЛЬТЕРНАТИВА: Ручной OCR**
|
||||
|
||||
### **Если нужен РУЧНОЙ OCR (не автоматический):**
|
||||
|
||||
Есть другое приложение: **OCR** (не workflow_ocr!)
|
||||
|
||||
**Установка:**
|
||||
```bash
|
||||
docker exec -u www-data nextcloud-fresh php occ app:install ocr
|
||||
```
|
||||
|
||||
**В нём есть:**
|
||||
- Кнопка "OCR" в меню файла
|
||||
- Ручной запуск для выбранных файлов
|
||||
- Выбор языка
|
||||
|
||||
**НО:** Это старое приложение, может не работать на Nextcloud 31
|
||||
|
||||
---
|
||||
|
||||
## 🎯 **ЧТО ДЕЛАТЬ СЕЙЧАС:**
|
||||
|
||||
### **Вариант А: Я создам правило автоматически**
|
||||
|
||||
Создам через команду, займёт 10 секунд:
|
||||
```sql
|
||||
INSERT INTO oc_flow_operations ...
|
||||
```
|
||||
|
||||
**Результат:** Все новые PDF в проектах автоматически пройдут OCR
|
||||
|
||||
---
|
||||
|
||||
### **Вариант Б: Ты создашь сам через интерфейс**
|
||||
|
||||
Я покажу пошагово со скриншотами:
|
||||
```
|
||||
Settings → Flow → Add rule → ...
|
||||
```
|
||||
|
||||
**Результат:** Ты понимаешь как создавать правила
|
||||
|
||||
---
|
||||
|
||||
### **Вариант В: Установить приложение для ручного OCR**
|
||||
|
||||
```bash
|
||||
docker exec -u www-data nextcloud-fresh php occ app:install ocr
|
||||
```
|
||||
|
||||
**Результат:** Появится кнопка OCR в меню файла
|
||||
|
||||
**⚠️ Внимание:** Может не работать на Nextcloud 31!
|
||||
|
||||
---
|
||||
|
||||
## 💡 **МОЯ РЕКОМЕНДАЦИЯ:**
|
||||
|
||||
**Используй АВТОМАТИЧЕСКИЙ OCR** (Workflow):
|
||||
- ✅ Настроил один раз → работает всегда
|
||||
- ✅ Не нужно помнить запускать OCR
|
||||
- ✅ Все документы обрабатываются одинаково
|
||||
- ✅ Современный подход
|
||||
|
||||
**Ручной OCR** нужен редко (только для старых файлов)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 **ДАВАЙ СОЗДАМ ПРАВИЛО?**
|
||||
|
||||
**Что создать:**
|
||||
|
||||
**Правило 1: Судебные документы**
|
||||
```
|
||||
PDF с словами: решение, определение, постановление, иск
|
||||
→ OCR (русский + английский)
|
||||
```
|
||||
|
||||
**Правило 2: Договоры**
|
||||
```
|
||||
PDF в папке Contracts
|
||||
→ OCR (русский + английский)
|
||||
```
|
||||
|
||||
**Правило 3: Все PDF в проектах < 10MB**
|
||||
```
|
||||
PDF в Documents/Project/*, размер < 10MB
|
||||
→ OCR (русский + английский)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Какое правило создать?** Или все три сразу? 😊
|
||||
|
||||
|
||||
277
WORKFLOW_ПРИЛОЖЕНИЯ_NEXTCLOUD.md
Normal file
277
WORKFLOW_ПРИЛОЖЕНИЯ_NEXTCLOUD.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# 🔄 WORKFLOW ПРИЛОЖЕНИЯ В NEXTCLOUD
|
||||
|
||||
## 1️⃣ **Workflow OCR Backend** (workflow_ocr)
|
||||
|
||||
### **ЧТО ЭТО:**
|
||||
Приложение для **автоматического распознавания текста (OCR)** из изображений и PDF файлов.
|
||||
|
||||
### **КАК РАБОТАЕТ:**
|
||||
```
|
||||
PDF/Изображение загружено →
|
||||
→ Workflow Engine видит событие →
|
||||
→ Tesseract OCR обрабатывает →
|
||||
→ Создаёт текстовый файл с распознанным текстом
|
||||
```
|
||||
|
||||
### **ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:**
|
||||
|
||||
**1. Автоматическое распознавание сканов:**
|
||||
```
|
||||
Правило: Если загружен файл *.pdf в папку /Scans
|
||||
Действие: Распознать текст и создать .txt файл
|
||||
```
|
||||
|
||||
**2. Обработка чеков:**
|
||||
```
|
||||
Правило: Если загружена фотография в /Receipts
|
||||
Действие: OCR → поиск по тексту
|
||||
```
|
||||
|
||||
**3. Юридические документы:**
|
||||
```
|
||||
Правило: Если загружен скан решения суда
|
||||
Действие: Распознать текст → сделать документ searchable
|
||||
```
|
||||
|
||||
### **НАСТРОЙКА:**
|
||||
|
||||
**1. Создать правило:**
|
||||
```
|
||||
Settings → Flow → Add rule:
|
||||
- When: File created
|
||||
- And: Mime type is image/* OR application/pdf
|
||||
- And: Path matches /Scans/*
|
||||
- Then: OCR processing
|
||||
```
|
||||
|
||||
**2. Выбрать язык:**
|
||||
- Russian (rus)
|
||||
- English (eng)
|
||||
- Multi (rus+eng)
|
||||
|
||||
### **ТЕКУЩИЙ СТАТУС:**
|
||||
- ✅ Установлено: workflow_ocr 1.31.3
|
||||
- ✅ Tesseract OCR: 5.5.0 (движок распознавания)
|
||||
- ❌ Правил НЕТ (не настроено)
|
||||
|
||||
**Вывод:** Приложение установлено, но **НЕ ИСПОЛЬЗУЕТСЯ** (нет правил).
|
||||
|
||||
---
|
||||
|
||||
## 2️⃣ **Flow Notifications** (flow_notifications)
|
||||
|
||||
### **ЧТО ЭТО:**
|
||||
Приложение для **отправки уведомлений** на основе событий Workflow.
|
||||
|
||||
### **КАК РАБОТАЕТ:**
|
||||
```
|
||||
Событие в Nextcloud →
|
||||
→ Workflow Engine проверяет правила →
|
||||
→ Flow Notifications отправляет уведомление
|
||||
```
|
||||
|
||||
### **ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:**
|
||||
|
||||
**1. Уведомление при загрузке важного файла:**
|
||||
```
|
||||
Правило: Если файл загружен в /Important
|
||||
Действие: Отправить уведомление "Загружен важный файл"
|
||||
```
|
||||
|
||||
**2. Уведомление о новом контракте:**
|
||||
```
|
||||
Правило: Если PDF загружен в /Contracts
|
||||
Действие: Уведомить юриста
|
||||
```
|
||||
|
||||
**3. Алерт при большом файле:**
|
||||
```
|
||||
Правило: Если размер файла > 100MB
|
||||
Действие: Уведомить администратора
|
||||
```
|
||||
|
||||
### **ТИПЫ УВЕДОМЛЕНИЙ:**
|
||||
- 🔔 Push-уведомления в браузере
|
||||
- 📧 Email
|
||||
- 📱 Nextcloud app (Android/iOS)
|
||||
- 🔗 Webhook (для интеграций)
|
||||
|
||||
### **НАСТРОЙКА:**
|
||||
|
||||
**Пример правила:**
|
||||
```
|
||||
Settings → Flow → Add rule:
|
||||
- When: File created
|
||||
- And: File size > 100000000 (100MB)
|
||||
- Then: Send notification
|
||||
- User: admin
|
||||
- Message: "Large file uploaded: {file}"
|
||||
```
|
||||
|
||||
### **ТЕКУЩИЙ СТАТУС:**
|
||||
- ✅ Установлено: flow_notifications 2.0.1
|
||||
- ❌ Правил НЕТ (не настроено)
|
||||
|
||||
**Вывод:** Приложение установлено, но **НЕ ИСПОЛЬЗУЕТСЯ**.
|
||||
|
||||
---
|
||||
|
||||
## 3️⃣ **Workflow Engine** (workflowengine)
|
||||
|
||||
### **ЧТО ЭТО:**
|
||||
**Основной движок** для всех workflow - автоматизация действий на основе событий.
|
||||
|
||||
### **АРХИТЕКТУРА:**
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ Workflow Engine │
|
||||
│ (ядро) │
|
||||
└────────┬─────────┘
|
||||
│
|
||||
┌─────────────────┼─────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
|
||||
│ workflow_ocr │ │flow_notifications││workflow_script│
|
||||
│ (OCR) │ │ (уведомления) │ │ (скрипты) │
|
||||
└───────────────┘ └───────────────┘ └───────────────┘
|
||||
```
|
||||
|
||||
### **ДОСТУПНЫЕ ДЕЙСТВИЯ:**
|
||||
|
||||
**Стандартные:**
|
||||
- ✅ Отправить уведомление
|
||||
- ✅ Добавить тег
|
||||
- ✅ Переместить файл
|
||||
- ✅ Конвертировать в PDF
|
||||
- ✅ Запустить скрипт
|
||||
|
||||
**С установленными приложениями:**
|
||||
- ✅ OCR обработка (workflow_ocr)
|
||||
- ✅ Конвертация PDF (workflow_pdf_converter)
|
||||
- ✅ Запуск скриптов (workflow_script)
|
||||
|
||||
---
|
||||
|
||||
## 4️⃣ **Другие установленные Workflow приложения:**
|
||||
|
||||
### **workflow_pdf_converter**
|
||||
Конвертация документов в PDF:
|
||||
- Word → PDF
|
||||
- Excel → PDF
|
||||
- Images → PDF
|
||||
|
||||
### **workflow_script**
|
||||
Запуск произвольных скриптов при событиях:
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Пример: отправка файла по API
|
||||
curl -X POST https://api.example.com/upload \
|
||||
-F "file=@$1"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 **ТЕКУЩАЯ СИТУАЦИЯ:**
|
||||
|
||||
### **Установлено:**
|
||||
```
|
||||
✅ workflowengine: 2.13.0 (ядро)
|
||||
✅ workflow_ocr: 1.31.3 (OCR)
|
||||
✅ workflow_pdf_converter: 2.0.0 (PDF конвертер)
|
||||
✅ workflow_script: 2.0.0 (скрипты)
|
||||
✅ flow_notifications: 2.0.1 (уведомления)
|
||||
```
|
||||
|
||||
### **Активных правил:** 0 ❌
|
||||
|
||||
**Вывод:** Все приложения установлены, но **НЕ НАСТРОЕНЫ** и **НЕ ИСПОЛЬЗУЮТСЯ**.
|
||||
|
||||
---
|
||||
|
||||
## 💡 **РЕКОМЕНДАЦИИ:**
|
||||
|
||||
### **Вариант 1: ИСПОЛЬЗОВАТЬ для автоматизации**
|
||||
|
||||
**Полезные сценарии для CRM:**
|
||||
|
||||
**1. OCR судебных решений:**
|
||||
```
|
||||
Правило: PDF загружен в Documents/Project/*/Судебные решения
|
||||
Действие: OCR → текст извлечён → можно искать
|
||||
```
|
||||
|
||||
**2. Уведомления о новых документах:**
|
||||
```
|
||||
Правило: Файл загружен в Documents/Project/*
|
||||
Действие: Уведомить ответственного юриста
|
||||
```
|
||||
|
||||
**3. Автоматическая конвертация в PDF:**
|
||||
```
|
||||
Правило: Word/Excel загружен
|
||||
Действие: Конвертировать в PDF
|
||||
```
|
||||
|
||||
### **Вариант 2: ОТКЛЮЧИТЬ если не нужно**
|
||||
|
||||
Если не планируете использовать автоматизацию:
|
||||
```bash
|
||||
docker exec -u www-data nextcloud-fresh php occ app:disable workflow_ocr
|
||||
docker exec -u www-data nextcloud-fresh php occ app:disable flow_notifications
|
||||
docker exec -u www-data nextcloud-fresh php occ app:disable workflow_pdf_converter
|
||||
docker exec -u www-data nextcloud-fresh php occ app:disable workflow_script
|
||||
```
|
||||
|
||||
**Оставить только ядро:** `workflowengine` (базовая функциональность)
|
||||
|
||||
---
|
||||
|
||||
## 🔧 **КАК НАСТРОИТЬ (если нужно):**
|
||||
|
||||
### **Шаг 1: Открыть настройки Flow**
|
||||
```
|
||||
https://office.clientright.ru:8443/settings/admin/workflow
|
||||
```
|
||||
|
||||
### **Шаг 2: Создать правило**
|
||||
```
|
||||
1. Add rule
|
||||
2. Выбрать событие (File created, File updated, etc.)
|
||||
3. Добавить условия (path, mime type, size)
|
||||
4. Выбрать действие (OCR, notification, convert)
|
||||
5. Save
|
||||
```
|
||||
|
||||
### **Шаг 3: Протестировать**
|
||||
Загрузить тестовый файл → проверить что правило сработало
|
||||
|
||||
---
|
||||
|
||||
## 📊 **ИСПОЛЬЗУЕМЫЕ РЕСУРСЫ:**
|
||||
|
||||
### **Tesseract OCR:**
|
||||
- Версия: 5.5.0
|
||||
- Языки: русский, английский (установлены)
|
||||
- CPU: ~50-100% на файл
|
||||
- Время: ~10-30 сек на страницу
|
||||
|
||||
### **Дисковое пространство:**
|
||||
OCR создаёт дополнительные текстовые файлы = +10-20% от размера PDF
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ **ВАЖНО:**
|
||||
|
||||
**Если НЕ используете эти функции** → лучше **отключить**:
|
||||
- Меньше нагрузка на систему
|
||||
- Меньше ошибок в логах
|
||||
- Проще поддержка
|
||||
|
||||
**Если НУЖНА автоматизация** → настроить правила workflow
|
||||
|
||||
---
|
||||
|
||||
**Вам нужна автоматизация (OCR, уведомления)?** Или отключить эти приложения? 🤔
|
||||
|
||||
|
||||
520
ai_dialogs_export.txt
Normal file
520
ai_dialogs_export.txt
Normal file
@@ -0,0 +1,520 @@
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ 📚 ИСТОРИЯ AI ДИАЛОГОВ С КЛИЕНТАМИ ║
|
||||
║ Дата выгрузки: 29.10.2025 12:01:36 ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
|
||||
Всего записей: 29
|
||||
|
||||
════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📁 ПРОЕКТ: Курманжанова ИП Жгенти Дав<D0B0> (ID: 36937)
|
||||
👤 КЛИЕНТ: Карина Курманжанова
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
┌─ Диалог #1 ─────────────────────────────────────────────────────────────────┐
|
||||
│ 📅 2025-10-20 11:37:53 │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ❓ ВОПРОС: │
|
||||
│ [document] │
|
||||
│ │
|
||||
│ 🤖 ОТВЕТ: │
|
||||
│ На основе найденных фрагментов, я могу │
|
||||
│ рассказать о требованиях к │
|
||||
│ документам: │
|
||||
│ │
|
||||
│ 1. Документы нужно предоставлять в │
|
||||
│ формате PDF │
|
||||
│ 2. Если у вас бумажные документы - их │
|
||||
│ можно сфотографировать, но важно │
|
||||
│ делать это ровно и четко, захватывая │
|
||||
│ все страницы │
|
||||
│ 3. Для подачи в суд документы лучше │
|
||||
│ собрать в один PDF-файл │
|
||||
│ 4. При личном участии в суде нужно │
|
||||
│ иметь с собой: │
|
||||
│ - распечатанные копии всех документов │
|
||||
│ - паспорт │
|
||||
│ - оригиналы документов │
|
||||
│ │
|
||||
│ Если вам нужна помощь с конкретными │
|
||||
│ документами по вашему делу, уточните │
|
||||
│ пожалуйста ваш вопрос. │
|
||||
│ │
|
||||
│ <b>Если у вас остались вопросы — │
|
||||
│ мы с радостью ответим!</b> │
|
||||
│ Напишите их прямо здесь. │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📁 ПРОЕКТ: Юдин ИП Николаенков Васили (ID: 87121)
|
||||
👤 КЛИЕНТ:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📁 ПРОЕКТ: Коробков _Тим_ДТП (ID: 354918)
|
||||
👤 КЛИЕНТ: Fedor -
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
┌─ Диалог #1 ─────────────────────────────────────────────────────────────────┐
|
||||
│ 📅 2025-10-16 16:39:10 │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ❓ ВОПРОС: │
|
||||
│ да, скажи пожалуйста, какой статус? │
|
||||
│ │
|
||||
│ 🤖 ОТВЕТ: │
|
||||
│ На данный момент дело инициировано │
|
||||
│ (находится в начальной стадии │
|
||||
│ претензионно-исковой работы). Это │
|
||||
│ значит, что работа по делу начата, но │
|
||||
│ до суда дело еще не дошло. В системе │
|
||||
│ есть загруженные документы, включая │
|
||||
│ претензию, но окончательное решение │
|
||||
│ по делу пока не принято. │
|
||||
│ │
|
||||
│ Отмечу важные моменты: │
|
||||
│ - Дело активно (не завершено) │
|
||||
│ - Находится на стадии досудебного │
|
||||
│ урегулирования │
|
||||
│ - Плановая дата завершения │
|
||||
│ установлена на 16.11.2025 │
|
||||
│ │
|
||||
│ <b>Если у вас остались вопросы — │
|
||||
│ мы с радостью ответим!</b> │
|
||||
│ Напишите их прямо здесь. │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📁 ПРОЕКТ: Казаков УНИВЕРСИТЕТ СИНЕРГ (ID: 370163)
|
||||
👤 КЛИЕНТ: Антон Казаков
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
┌─ Диалог #1 ─────────────────────────────────────────────────────────────────┐
|
||||
│ 📅 2025-10-10 11:35:19 │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ❓ ВОПРОС: │
|
||||
│ Подробную статью Гайд давай │
|
||||
│ │
|
||||
│ 🤖 ОТВЕТ: │
|
||||
│ Простите, но я не могу составить для │
|
||||
│ вас подробную статью-гайд или │
|
||||
│ рекомендации для публикации. Это │
|
||||
│ выходит за рамки моих полномочий как │
|
||||
│ юридического помощника по вашему │
|
||||
│ делу. │
|
||||
│ │
|
||||
│ Вместо этого я рекомендую: │
|
||||
│ │
|
||||
│ 1. Обратиться к вашему юристу, который │
|
||||
│ вел дело - он сможет дать │
|
||||
│ квалифицированную консультацию о том, │
|
||||
│ как безопасно делиться информацией о │
|
||||
│ деле. │
|
||||
│ │
|
||||
│ 2. Любые публикации о деле лучше │
|
||||
│ предварительно согласовать с вашим │
|
||||
│ юристом, чтобы избежать возможных │
|
||||
│ рисков. │
|
||||
│ │
|
||||
│ Я могу отвечать на ваши вопросы по │
|
||||
│ существу дела: о датах заседаний, │
|
||||
│ суммах, статусе, имеющихся документах. │
|
||||
│ Но составление рекомендаций для │
|
||||
│ публикации - это отдельная │
|
||||
│ юридическая услуга, которая требует │
|
||||
│ участия вашего представителя. │
|
||||
│ │
|
||||
│ Хотите узнать конкретную информацию │
|
||||
│ по вашему делу? │
|
||||
│ │
|
||||
│ <b>Если у вас остались вопросы — │
|
||||
│ мы с радостью ответим!</b> │
|
||||
│ Напишите их прямо здесь. │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📁 ПРОЕКТ: Сатюкова ООО ЭДЭКС (ID: 379166)
|
||||
👤 КЛИЕНТ: Дария Сатюкова
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
┌─ Диалог #1 ─────────────────────────────────────────────────────────────────┐
|
||||
│ 📅 2025-10-14 20:20:03 │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ❓ ВОПРОС: │
|
||||
│ [photo] │
|
||||
│ │
|
||||
│ 🤖 ОТВЕТ: │
|
||||
│ Я не могу обрабатывать или │
|
||||
│ анализировать фотографии. Если у вас │
|
||||
│ есть вопрос по вашему делу, │
|
||||
│ пожалуйста, задайте его текстом, и я │
|
||||
│ постараюсь помочь найти нужную │
|
||||
│ информацию. │
|
||||
│ │
|
||||
│ <b>Если у вас остались вопросы — │
|
||||
│ мы с радостью ответим!</b> │
|
||||
│ Напишите их прямо здесь. │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─ Диалог #2 ─────────────────────────────────────────────────────────────────┐
|
||||
│ 📅 2025-10-14 20:20:53 │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ❓ ВОПРОС: │
|
||||
│ Мне прислали письмо о неявке в суд │
|
||||
│ │
|
||||
│ 🤖 ОТВЕТ: │
|
||||
│ Если вы получили письмо о неявке в суд, │
|
||||
│ это важное уведомление, которое │
|
||||
│ требует вашего внимания. Давайте я │
|
||||
│ уточню детали вашей ситуации: │
|
||||
│ │
|
||||
│ 1. Получите письмо и внимательно │
|
||||
│ ознакомьтесь с его содержанием │
|
||||
│ 2. Если вы действительно не можете │
|
||||
│ присутствовать на судебном заседании, │
|
||||
│ вам нужно уведомить суд о причинах │
|
||||
│ неявки │
|
||||
│ 3. Для получения более точной │
|
||||
│ информации о необходимых действиях в │
|
||||
│ вашей конкретной ситуации, рекомендую │
|
||||
│ обратиться к вашему юристу, который │
|
||||
│ ведет ваше дело │
|
||||
│ │
|
||||
│ Также вы можете отслеживать │
|
||||
│ информацию о деле, позвонив │
|
||||
│ непосредственно в суд. │
|
||||
│ │
|
||||
│ Для того чтобы дать вам более точный │
|
||||
│ ответ по вашей конкретной ситуации, │
|
||||
│ мне нужны дополнительные детали. │
|
||||
│ Пожалуйста, уточните: │
|
||||
│ - Получили ли вы уже само письмо или │
|
||||
│ только уведомление о нем? │
|
||||
│ - От какого суда пришло письмо? │
|
||||
│ - На какую дату назначено заседание? │
|
||||
│ │
|
||||
│ <b>Если у вас остались вопросы — │
|
||||
│ мы с радостью ответим!</b> │
|
||||
│ Напишите их прямо здесь. │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─ Диалог #3 ─────────────────────────────────────────────────────────────────┐
|
||||
│ 📅 2025-10-14 20:22:36 │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ❓ ВОПРОС: │
|
||||
│ Я уже получила письмо │
|
||||
│ От Гагаринского районного суда города │
|
||||
│ Москвы │
|
||||
│ На 25.11.2025 │
|
||||
│ │
|
||||
│ 🤖 ОТВЕТ: │
|
||||
│ Если вы не можете присутствовать на │
|
||||
│ судебном заседании 25.11.2025 в │
|
||||
│ Гагаринском районном суде города │
|
||||
│ Москвы, вам необходимо: │
|
||||
│ │
|
||||
│ 1. Подать в суд ходатайство об │
|
||||
│ отложении дела или о рассмотрении │
|
||||
│ дела в ваше отсутствие. Это можно │
|
||||
│ сделать: │
|
||||
│ - через сайт суда в электронном виде │
|
||||
│ - направить почтой │
|
||||
│ - подать непосредственно в │
|
||||
│ канцелярию суда │
|
||||
│ │
|
||||
│ 2. В ходатайстве нужно: │
|
||||
│ - указать причины вашей неявки │
|
||||
│ - приложить подтверждающие │
|
||||
│ документы (если есть) │
|
||||
│ - указать, просите ли вы рассмотреть │
|
||||
│ дело без вашего участия или перенести │
|
||||
│ заседание │
|
||||
│ │
|
||||
│ Для получения более подробной │
|
||||
│ консультации по вашей конкретной │
|
||||
│ ситуации рекомендую обратиться к │
|
||||
│ вашему юристу, который ведет ваше │
|
||||
│ дело. Он лучше знает обстоятельства и │
|
||||
│ сможет подсказать оптимальное │
|
||||
│ решение. │
|
||||
│ │
|
||||
│ Также вы можете отслеживать │
|
||||
│ информацию о движении дела на │
|
||||
│ официальном сайте суда. │
|
||||
│ │
|
||||
│ <b>Если у вас остались вопросы — │
|
||||
│ мы с радостью ответим!</b> │
|
||||
│ Напишите их прямо здесь. │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─ Диалог #4 ─────────────────────────────────────────────────────────────────┐
|
||||
│ 📅 2025-10-16 16:19:25 │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ❓ ВОПРОС: │
|
||||
│ Я уже получила письмо от Гагаринского │
|
||||
│ районного суда │
|
||||
│ │
|
||||
│ 🤖 ОТВЕТ: │
|
||||
│ Если вы не можете присутствовать на │
|
||||
│ судебном заседании... │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📁 ПРОЕКТ: Гафиев ООО ЭДЭКС (ID: 384256)
|
||||
👤 КЛИЕНТ: Марсель Гафиев
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
┌─ Диалог #1 ─────────────────────────────────────────────────────────────────┐
|
||||
│ 📅 2025-10-25 17:39:54 │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ❓ ВОПРОС: │
|
||||
│ Здравствуйте! Подготовьте пожалуйста │
|
||||
│ памятку о дольнейсших действиях, │
|
||||
│ (исполнении решения, и сроки) │
|
||||
│ благодарю. │
|
||||
│ │
|
||||
│ 🤖 ОТВЕТ: │
|
||||
│ На основании имеющейся информации, │
|
||||
│ вот памятка о дальнейших действиях по │
|
||||
│ исполнению решения суда: │
|
||||
│ │
|
||||
│ 1. Получение исполнительного листа: │
|
||||
│ - После вступления решения в законную │
|
||||
│ силу подайте заявление в суд о выдаче │
|
||||
│ исполнительного листа │
|
||||
│ - Вы можете получить исполнительный │
|
||||
│ лист лично в суде или попросить │
|
||||
│ направить его почтой │
|
||||
│ - Срок для получения исполнительного │
|
||||
│ листа - 3 года с момента вступления │
|
||||
│ решения в законную силу │
|
||||
│ │
|
||||
│ 2. Сроки действия исполнительного │
|
||||
│ листа: │
|
||||
│ - Исполнительный лист действует 3 года │
|
||||
│ с момента его выдачи │
|
||||
│ - В течение этого срока вы можете │
|
||||
│ предъявить его к исполнению │
|
||||
│ │
|
||||
│ 3. Порядок исполнения: │
|
||||
│ - После получения исполнительного │
|
||||
│ листа вы можете: │
|
||||
│ * Предъявить его в службу судебных │
|
||||
│ приставов │
|
||||
│ * Если у вас есть банковские │
|
||||
│ реквизиты должника, направить │
|
||||
│ исполнительный лист напрямую в банк │
|
||||
│ │
|
||||
│ Если у вас возникнут вопросы по │
|
||||
│ конкретным действиям или потребуется │
|
||||
│ дополнительная информация, │
|
||||
│ обратитесь к вашему юристу, который │
|
||||
│ сможет дать более детальные │
|
||||
│ рекомендации с учетом особенностей │
|
||||
│ вашего дела. │
|
||||
│ │
|
||||
│ <b>Если у вас остались вопросы — │
|
||||
│ мы с радостью ответим!</b> │
|
||||
│ Напишите их прямо здесь. │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📁 ПРОЕКТ: Лучина ООО ОТКРЫТЫЙ МИР (ID: 392972)
|
||||
👤 КЛИЕНТ: Галина Лучина
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
┌─ Диалог #1 ─────────────────────────────────────────────────────────────────┐
|
||||
│ 📅 2025-10-22 09:49:48 │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ❓ ВОПРОС: │
|
||||
│ Возврат денежных средств выполнен в │
|
||||
│ полном объёме │
|
||||
│ │
|
||||
│ 🤖 ОТВЕТ: │
|
||||
│ По имеющимся данным, возвращена │
|
||||
│ только часть суммы: │
|
||||
│ - Цена договора составляла 130 960,00 ₽ │
|
||||
│ - Возвращено 57 456 ₽ (частичный возврат, │
|
||||
│ выполнен 29.09.2025) │
|
||||
│ │
|
||||
│ Таким образом, возврат денежных │
|
||||
│ средств выполнен не в полном объеме. │
|
||||
│ Остаток задолженности составляет 73 │
|
||||
│ 504,00 ₽. │
|
||||
│ │
|
||||
│ Дело находится в претензионной │
|
||||
│ стадии, ожидается ответ на претензию │
|
||||
│ от ООО "Открытый Мир". │
|
||||
│ │
|
||||
│ <b>Если у вас остались вопросы — │
|
||||
│ мы с радостью ответим!</b> │
|
||||
│ Напишите их прямо здесь. │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─ Диалог #2 ─────────────────────────────────────────────────────────────────┐
|
||||
│ 📅 2025-10-22 09:51:29 │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ❓ ВОПРОС: │
|
||||
│ Возврат выполнен 18.10 по итогам спора, │
|
||||
│ открытого банком, картой которого │
|
||||
│ произведена оплата. │
|
||||
│ │
|
||||
│ 🤖 ОТВЕТ: │
|
||||
│ В данных нет информации о возврате │
|
||||
│ денежных средств 18.10 через банк. Для │
|
||||
│ корректного отражения в деле этой │
|
||||
│ информации рекомендую: │
|
||||
│ │
|
||||
│ 1. Предоставить подтверждающие │
|
||||
│ документы о возврате (чек, выписка или │
|
||||
│ уведомление от банка) │
|
||||
│ 2. После этого информация о полном │
|
||||
│ возврате будет добавлена в карточку │
|
||||
│ дела │
|
||||
│ │
|
||||
│ На текущий момент в деле отражен │
|
||||
│ только частичный возврат 57 456 ₽ от │
|
||||
│ 29.09.2025. Если у вас есть документы о │
|
||||
│ дополнительном возврате, пожалуйста, │
|
||||
│ направьте их вашему юристу для │
|
||||
│ актуализации информации по делу. │
|
||||
│ │
|
||||
│ <b>Если у вас остались вопросы — │
|
||||
│ мы с радостью ответим!</b> │
|
||||
│ Напишите их прямо здесь. │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─ Диалог #3 ─────────────────────────────────────────────────────────────────┐
|
||||
│ 📅 2025-10-22 09:59:28 │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ❓ ВОПРОС: │
|
||||
│ [photo] │
|
||||
│ │
|
||||
│ 🤖 ОТВЕТ: │
|
||||
│ Да, я вижу подтверждение вашей │
|
||||
│ информации. Действительно, возврат │
|
||||
│ денежных средств по спору, │
|
||||
│ инициированному банком, произошел. │
|
||||
│ Однако для корректного отражения этой │
|
||||
│ информации в деле нужно: │
|
||||
│ │
|
||||
│ 1. Предоставить подтверждающие │
|
||||
│ документы о возврате (на выбор): │
|
||||
│ - чек │
|
||||
│ - выписка │
|
||||
│ - уведомление от банка │
|
||||
│ │
|
||||
│ После предоставления подтверждающих │
|
||||
│ документов информация о полном │
|
||||
│ возврате будет добавлена в карточку │
|
||||
│ дела. │
|
||||
│ │
|
||||
│ На данный момент в деле отражен только │
|
||||
│ первый частичный возврат в размере 57 │
|
||||
│ 456 ₽ от 29.09.2025. │
|
||||
│ │
|
||||
│ <b>Если у вас остались вопросы — │
|
||||
│ мы с радостью ответим!</b> │
|
||||
│ Напишите их прямо здесь. │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─ Диалог #4 ─────────────────────────────────────────────────────────────────┐
|
||||
│ 📅 2025-10-22 10:03:46 │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ❓ ВОПРОС: │
|
||||
│ [photo] │
|
||||
│ │
|
||||
│ 🤖 ОТВЕТ: │
|
||||
│ В деле действительно сейчас учтен │
|
||||
│ только частичный возврат денежных │
|
||||
│ средств в размере 54 567 ₽. Чтобы │
|
||||
│ зафиксировать полный возврат по спору │
|
||||
│ через банк от 18.10, пожалуйста, │
|
||||
│ предоставьте любой из подтверждающих │
|
||||
│ документов: │
|
||||
│ │
|
||||
│ - чек, или │
|
||||
│ - выписку, или │
|
||||
│ - уведомление от банка │
|
||||
│ │
|
||||
│ После получения документа информация │
|
||||
│ о полном возврате денежных средств │
|
||||
│ будет добавлена в материалы дела. │
|
||||
│ │
|
||||
│ <b>Если у вас остались вопросы — │
|
||||
│ мы с радостью ответим!</b> │
|
||||
│ Напишите их прямо здесь. │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─ Диалог #5 ─────────────────────────────────────────────────────────────────┐
|
||||
│ 📅 2025-10-22 10:03:50 │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ❓ ВОПРОС: │
|
||||
│ [photo] │
|
||||
│ │
|
||||
│ 🤖 ОТВЕТ: │
|
||||
│ Я вижу фотоподтверждение возврата │
|
||||
│ средств. Для корректного отражения │
|
||||
│ полного возврата в деле необходимо │
|
||||
│ предоставить один из официальных │
|
||||
│ подтверждающих документов: │
|
||||
│ │
|
||||
│ 1. Чек │
|
||||
│ 2. Банковскую выписку │
|
||||
│ 3. Уведомление от банка о возврате │
|
||||
│ средств │
|
||||
│ │
|
||||
│ После получения любого из этих │
|
||||
│ документов информация о полном │
|
||||
│ возврате будет добавлена в карточку │
|
||||
│ дела. │
|
||||
│ │
|
||||
│ На текущий момент в деле отражен │
|
||||
│ только частичный возврат в размере 57 │
|
||||
│ 456 ₽. │
|
||||
│ │
|
||||
│ <b>Если у вас остались вопросы — │
|
||||
│ мы с радостью ответим!</b> │
|
||||
│ Напишите их прямо здесь. │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─ Диалог #6 ─────────────────────────────────────────────────────────────────┐
|
||||
│ 📅 2025-10-22 10:04:43 │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ❓ ВОПРОС: │
|
||||
│ Иных документов не имею. │
|
||||
│ │
|
||||
│ 🤖 ОТВЕТ: │
|
||||
│ Для корректного отражения полного │
|
||||
│ возврата средств в деле, необходимо │
|
||||
│ предоставить один из следующих │
|
||||
│ подтверждающих документов: │
|
||||
│ │
|
||||
│ 1. Чек │
|
||||
│ 2. Банковскую выписку │
|
||||
│ 3. Уведомление от банка о возврате │
|
||||
│ средств │
|
||||
│ │
|
||||
│ На данный момент в деле учтен только │
|
||||
│ частичный возврат в размере 57 456 ₽. │
|
||||
│ │
|
||||
│ После предоставления любого из │
|
||||
│ указанных документов, информация о │
|
||||
│ полном возврате будет добавлена в │
|
||||
│ карточку дела. │
|
||||
│ │
|
||||
│ <b>Если у вас остались вопросы — │
|
||||
│ мы с радостью ответим!</b> │
|
||||
│ Напишите их прямо здесь. │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
════════════════════════════════════════════════════════════════════════════════
|
||||
Конец выгрузки
|
||||
416
aiassist/ai_sse.php
Normal file
416
aiassist/ai_sse.php
Normal file
@@ -0,0 +1,416 @@
|
||||
<?php
|
||||
/**
|
||||
* SSE endpoint для AI Drawer через Redis Pub/Sub
|
||||
*
|
||||
* Подписывается на Redis канал и отправляет ответы AI через SSE
|
||||
*/
|
||||
|
||||
// Отключаем буферизацию
|
||||
while (@ob_end_flush());
|
||||
|
||||
// Настройки SSE
|
||||
header('Content-Type: text/event-stream');
|
||||
header('Cache-Control: no-cache');
|
||||
header('Connection: keep-alive');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Отключаем лимит времени
|
||||
@ini_set('zlib.output_compression', 0);
|
||||
@ini_set('implicit_flush', 1);
|
||||
set_time_limit(0);
|
||||
ignore_user_abort(false);
|
||||
|
||||
if (!function_exists('ai_sse_debug_log')) {
|
||||
function ai_sse_debug_log($message) {
|
||||
$logFile = '/var/www/fastuser/data/www/crm.clientright.ru/logs/ai_sse_debug.log';
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
file_put_contents($logFile, "[{$timestamp}] {$message}\n", FILE_APPEND);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('ai_sse_cache_response')) {
|
||||
function ai_sse_cache_response($taskId, $responseText, $status = 'completed') {
|
||||
$cacheKey = "ai:response:cache:{$taskId}";
|
||||
$payload = [
|
||||
'task_id' => $taskId,
|
||||
'response' => $responseText,
|
||||
'status' => $status,
|
||||
'timestamp' => date('Y-m-d H:i:s')
|
||||
];
|
||||
$cacheValue = json_encode($payload, JSON_UNESCAPED_UNICODE);
|
||||
ai_sse_debug_log("{$taskId}: caching response (status={$status})");
|
||||
try {
|
||||
if (class_exists('Redis')) {
|
||||
$cacheRedis = new Redis();
|
||||
if (!$cacheRedis->connect('crm.clientright.ru', 6379)) {
|
||||
throw new Exception('Redis cache connection failed');
|
||||
}
|
||||
$cacheRedis->auth('CRM_Redis_Pass_2025_Secure!');
|
||||
$result = $cacheRedis->setex($cacheKey, 300, $cacheValue);
|
||||
$cacheRedis->close();
|
||||
ai_sse_debug_log("{$taskId}: cache set via phpredis result=" . var_export($result, true));
|
||||
return (bool)$result;
|
||||
}
|
||||
|
||||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/vendor/autoload.php';
|
||||
$cacheRedis = new Predis\Client([
|
||||
'scheme' => 'tcp',
|
||||
'host' => 'crm.clientright.ru',
|
||||
'port' => 6379,
|
||||
'password' => 'CRM_Redis_Pass_2025_Secure!',
|
||||
]);
|
||||
$result = $cacheRedis->setex($cacheKey, 300, $cacheValue);
|
||||
ai_sse_debug_log("{$taskId}: cache set via Predis result=" . var_export($result, true));
|
||||
return (bool)$result;
|
||||
} catch (Throwable $e) {
|
||||
$message = "Failed to cache response for {$taskId}: " . $e->getMessage();
|
||||
error_log("[AI SSE] {$message}");
|
||||
ai_sse_debug_log("{$taskId}: {$message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Отправляем начальный padding для Nginx
|
||||
echo str_repeat(' ', 4096);
|
||||
echo "\n\n";
|
||||
flush();
|
||||
|
||||
// Функция для отправки события
|
||||
function sendSSE($type, $data) {
|
||||
// Для кастомных событий используем event:, для обычных data:
|
||||
if (in_array($type, ['connected', 'response', 'error', 'heartbeat'])) {
|
||||
echo "event: {$type}\n";
|
||||
}
|
||||
echo "data: " . json_encode([
|
||||
'type' => $type,
|
||||
'data' => $data,
|
||||
'timestamp' => date('Y-m-d H:i:s')
|
||||
], JSON_UNESCAPED_UNICODE) . "\n\n";
|
||||
flush();
|
||||
}
|
||||
|
||||
try {
|
||||
// Получаем task_id из GET параметра
|
||||
$taskId = $_GET['task_id'] ?? null;
|
||||
|
||||
if (!$taskId) {
|
||||
sendSSE('error', ['message' => 'Missing task_id parameter']);
|
||||
exit();
|
||||
}
|
||||
|
||||
error_log("[AI SSE] Starting SSE connection for task_id: {$taskId}");
|
||||
|
||||
// Отправляем событие подключения
|
||||
sendSSE('connected', [
|
||||
'message' => 'Подключено к AI событиям',
|
||||
'task_id' => $taskId
|
||||
]);
|
||||
|
||||
// Сначала проверяем - может ответ уже есть в Redis ключе (если публикация была до подписки)
|
||||
$cacheKey = "ai:response:cache:{$taskId}";
|
||||
$cachedResponse = null;
|
||||
|
||||
try {
|
||||
if (class_exists('Redis')) {
|
||||
$checkRedis = new Redis();
|
||||
if ($checkRedis->connect('crm.clientright.ru', 6379)) {
|
||||
$checkRedis->auth('CRM_Redis_Pass_2025_Secure!');
|
||||
$cachedResponse = $checkRedis->get($cacheKey);
|
||||
$checkRedis->close();
|
||||
}
|
||||
} else {
|
||||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/vendor/autoload.php';
|
||||
$checkRedis = new Predis\Client([
|
||||
'scheme' => 'tcp',
|
||||
'host' => 'crm.clientright.ru',
|
||||
'port' => 6379,
|
||||
'password' => 'CRM_Redis_Pass_2025_Secure!',
|
||||
]);
|
||||
$cachedResponse = $checkRedis->get($cacheKey);
|
||||
}
|
||||
|
||||
if ($cachedResponse) {
|
||||
error_log("[AI SSE] Found cached response in Redis key: {$cacheKey}");
|
||||
$responseData = json_decode($cachedResponse, true);
|
||||
|
||||
if ($responseData && isset($responseData['response'])) {
|
||||
sendSSE('response', [
|
||||
'task_id' => $taskId,
|
||||
'response' => $responseData['response']
|
||||
]);
|
||||
exit();
|
||||
} elseif ($cachedResponse && !$responseData) {
|
||||
// Если это просто строка
|
||||
sendSSE('response', [
|
||||
'task_id' => $taskId,
|
||||
'response' => $cachedResponse
|
||||
]);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("[AI SSE] Error checking cache: " . $e->getMessage());
|
||||
// Продолжаем с подпиской на канал
|
||||
}
|
||||
|
||||
// Подключаемся к Redis
|
||||
if (class_exists('Redis')) {
|
||||
// Используем расширение Redis
|
||||
$redis = new Redis();
|
||||
if (!$redis->connect('crm.clientright.ru', 6379)) {
|
||||
throw new Exception('Redis connection failed');
|
||||
}
|
||||
$redis->auth('CRM_Redis_Pass_2025_Secure!');
|
||||
|
||||
// Отправляем начальное событие
|
||||
sendSSE('connected', [
|
||||
'message' => 'Подключено к AI событиям',
|
||||
'task_id' => $taskId
|
||||
]);
|
||||
|
||||
// Подписываемся на канал для конкретного task_id
|
||||
$channel = "ai:response:{$taskId}";
|
||||
error_log("[AI SSE] Subscribing to channel: {$channel}");
|
||||
|
||||
// Используем правильный метод subscribe для расширения Redis
|
||||
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1); // Без таймаута
|
||||
|
||||
$redis->subscribe([$channel], function($redis, $channel, $message) use ($taskId) {
|
||||
error_log("[AI SSE] Received message on channel {$channel}, length: " . strlen($message));
|
||||
error_log("[AI SSE] Message preview: " . substr($message, 0, 500));
|
||||
|
||||
// Декодируем событие (может быть JSON или просто строка)
|
||||
$event = json_decode($message, true);
|
||||
|
||||
// Если это не JSON, значит n8n отправил просто строку ответа - ОТЛИЧНО!
|
||||
if (!$event || !is_array($event)) {
|
||||
error_log("[AI SSE] Message is plain text (not JSON), treating as response");
|
||||
ai_sse_debug_log("{$taskId}: plain text message received");
|
||||
|
||||
$cacheStored = ai_sse_cache_response($taskId, $message);
|
||||
if ($cacheStored) {
|
||||
error_log("[AI SSE] Cached plain text response for {$taskId}");
|
||||
ai_sse_debug_log("{$taskId}: plain text response cached successfully");
|
||||
} else {
|
||||
error_log("[AI SSE] Failed to cache plain text response for {$taskId}");
|
||||
}
|
||||
|
||||
// Отправляем ответ клиенту
|
||||
sendSSE('response', [
|
||||
'task_id' => $taskId,
|
||||
'response' => $message // Используем сообщение как есть
|
||||
]);
|
||||
error_log("[AI SSE] Response sent to client, unsubscribing");
|
||||
$redis->unsubscribe([$channel]);
|
||||
return;
|
||||
}
|
||||
|
||||
error_log("[AI SSE] Decoded event: " . json_encode($event, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
// Проверяем что это сообщение для нашего task_id
|
||||
// Принимаем если task_id совпадает ИЛИ если это просто ответ (может быть без task_id)
|
||||
$eventTaskId = $event['task_id'] ?? $event['taskId'] ?? null;
|
||||
$isOurTask = ($eventTaskId === $taskId) || ($eventTaskId === null && $channel === "ai:response:{$taskId}");
|
||||
|
||||
if ($isOurTask) {
|
||||
// Отправляем событие клиенту
|
||||
if (isset($event['response']) && !empty($event['response'])) {
|
||||
error_log("[AI SSE] Sending response event for task {$taskId}");
|
||||
ai_sse_debug_log("{$taskId}: JSON response received");
|
||||
|
||||
$cacheStored = ai_sse_cache_response($taskId, $event['response'], $event['status'] ?? 'completed');
|
||||
if ($cacheStored) {
|
||||
error_log("[AI SSE] Cached JSON response for {$taskId}");
|
||||
ai_sse_debug_log("{$taskId}: cached JSON response");
|
||||
} else {
|
||||
error_log("[AI SSE] Failed to cache JSON response for {$taskId}");
|
||||
}
|
||||
|
||||
sendSSE('response', [
|
||||
'task_id' => $taskId,
|
||||
'response' => $event['response']
|
||||
]);
|
||||
// Отписываемся после получения ответа
|
||||
$redis->unsubscribe([$channel]);
|
||||
} elseif (isset($event['error']) && !empty($event['error'])) {
|
||||
error_log("[AI SSE] Sending error event for task {$taskId}");
|
||||
sendSSE('error', [
|
||||
'task_id' => $taskId,
|
||||
'error' => $event['error']
|
||||
]);
|
||||
// Отписываемся после получения ошибки
|
||||
$redis->unsubscribe([$channel]);
|
||||
} else {
|
||||
error_log("[AI SSE] Event received but no response/error field. Event keys: " . implode(', ', array_keys($event)));
|
||||
// Если есть другие поля, попробуем найти ответ
|
||||
$possibleResponse = $event['message'] ?? $event['text'] ?? $event['content'] ?? null;
|
||||
if ($possibleResponse) {
|
||||
error_log("[AI SSE] Found response in alternative field");
|
||||
ai_sse_debug_log("{$taskId}: alternative response field detected");
|
||||
|
||||
$cacheStored = ai_sse_cache_response($taskId, $possibleResponse);
|
||||
if ($cacheStored) {
|
||||
error_log("[AI SSE] Cached alternative response for {$taskId}");
|
||||
ai_sse_debug_log("{$taskId}: cached alternative response");
|
||||
} else {
|
||||
error_log("[AI SSE] Failed to cache alternative response for {$taskId}");
|
||||
}
|
||||
|
||||
sendSSE('response', [
|
||||
'task_id' => $taskId,
|
||||
'response' => $possibleResponse
|
||||
]);
|
||||
$redis->unsubscribe([$channel]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error_log("[AI SSE] Event task_id mismatch: expected {$taskId}, got {$eventTaskId}");
|
||||
}
|
||||
|
||||
// Проверяем не отключился ли клиент
|
||||
if (connection_aborted()) {
|
||||
error_log("[AI SSE] Client disconnected, unsubscribing");
|
||||
$redis->unsubscribe([$channel]);
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
// Используем Predis через Composer
|
||||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/vendor/autoload.php';
|
||||
|
||||
$redis = new Predis\Client([
|
||||
'scheme' => 'tcp',
|
||||
'host' => 'crm.clientright.ru',
|
||||
'port' => 6379,
|
||||
'password' => 'CRM_Redis_Pass_2025_Secure!',
|
||||
'database' => 0,
|
||||
]);
|
||||
|
||||
// Отправляем начальное событие
|
||||
sendSSE('connected', [
|
||||
'message' => 'Подключено к AI событиям через Predis',
|
||||
'task_id' => $taskId
|
||||
]);
|
||||
|
||||
// Подписываемся на канал
|
||||
$channel = "ai:response:{$taskId}";
|
||||
$pubsub = $redis->pubSubLoop();
|
||||
$pubsub->subscribe($channel);
|
||||
|
||||
$lastHeartbeat = time();
|
||||
|
||||
foreach ($pubsub as $message) {
|
||||
// Heartbeat каждые 15 секунд
|
||||
if (time() - $lastHeartbeat > 15) {
|
||||
sendSSE('heartbeat', ['timestamp' => time()]);
|
||||
$lastHeartbeat = time();
|
||||
}
|
||||
|
||||
// Обрабатываем только сообщения
|
||||
if ($message->kind === 'message') {
|
||||
error_log("[AI SSE] Received message via Predis on channel {$channel}: " . substr($message->payload, 0, 200));
|
||||
|
||||
$event = json_decode($message->payload, true);
|
||||
|
||||
// Если это не JSON, значит n8n отправил просто строку ответа
|
||||
if (!$event || !is_array($event)) {
|
||||
error_log("[AI SSE] Message is not JSON via Predis, treating as plain text response");
|
||||
ai_sse_debug_log("{$taskId}: plain text message via Predis");
|
||||
|
||||
$cacheStored = ai_sse_cache_response($taskId, $message->payload);
|
||||
if ($cacheStored) {
|
||||
error_log("[AI SSE] Cached plain text response via Predis for {$taskId}");
|
||||
ai_sse_debug_log("{$taskId}: cached plain text via Predis");
|
||||
} else {
|
||||
error_log("[AI SSE] Failed to cache plain text response via Predis for {$taskId}");
|
||||
}
|
||||
|
||||
// Если это просто строка - отправляем как ответ
|
||||
sendSSE('response', [
|
||||
'task_id' => $taskId,
|
||||
'response' => $message->payload // Используем сообщение как есть
|
||||
]);
|
||||
$pubsub->unsubscribe($channel);
|
||||
break;
|
||||
}
|
||||
|
||||
error_log("[AI SSE] Decoded event via Predis: " . json_encode($event, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
// Проверяем что это сообщение для нашего task_id
|
||||
$eventTaskId = $event['task_id'] ?? $event['taskId'] ?? null;
|
||||
$isOurTask = ($eventTaskId === $taskId) || ($eventTaskId === null && $channel === "ai:response:{$taskId}");
|
||||
|
||||
if ($isOurTask) {
|
||||
if (isset($event['response']) && !empty($event['response'])) {
|
||||
error_log("[AI SSE] Sending response event via Predis for task {$taskId}");
|
||||
ai_sse_debug_log("{$taskId}: JSON response via Predis");
|
||||
|
||||
$cacheStored = ai_sse_cache_response($taskId, $event['response'], $event['status'] ?? 'completed');
|
||||
if ($cacheStored) {
|
||||
error_log("[AI SSE] Cached JSON response via Predis for {$taskId}");
|
||||
ai_sse_debug_log("{$taskId}: cached JSON via Predis");
|
||||
} else {
|
||||
error_log("[AI SSE] Failed to cache JSON response via Predis for {$taskId}");
|
||||
}
|
||||
|
||||
sendSSE('response', [
|
||||
'task_id' => $taskId,
|
||||
'response' => $event['response']
|
||||
]);
|
||||
// Отписываемся после получения ответа
|
||||
$pubsub->unsubscribe($channel);
|
||||
break;
|
||||
} elseif (isset($event['error']) && !empty($event['error'])) {
|
||||
error_log("[AI SSE] Sending error event via Predis for task {$taskId}");
|
||||
sendSSE('error', [
|
||||
'task_id' => $taskId,
|
||||
'error' => $event['error']
|
||||
]);
|
||||
// Отписываемся после получения ошибки
|
||||
$pubsub->unsubscribe($channel);
|
||||
break;
|
||||
} else {
|
||||
error_log("[AI SSE] Event received via Predis but no response/error field. Event keys: " . implode(', ', array_keys($event)));
|
||||
// Если есть другие поля, попробуем найти ответ
|
||||
$possibleResponse = $event['message'] ?? $event['text'] ?? $event['content'] ?? null;
|
||||
if ($possibleResponse) {
|
||||
error_log("[AI SSE] Found response in alternative field via Predis");
|
||||
ai_sse_debug_log("{$taskId}: alternative response via Predis");
|
||||
|
||||
$cacheStored = ai_sse_cache_response($taskId, $possibleResponse);
|
||||
if ($cacheStored) {
|
||||
error_log("[AI SSE] Cached alternative response via Predis for {$taskId}");
|
||||
ai_sse_debug_log("{$taskId}: cached alt response via Predis");
|
||||
} else {
|
||||
error_log("[AI SSE] Failed to cache alternative response via Predis for {$taskId}");
|
||||
}
|
||||
|
||||
sendSSE('response', [
|
||||
'task_id' => $taskId,
|
||||
'response' => $possibleResponse
|
||||
]);
|
||||
$pubsub->unsubscribe($channel);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error_log("[AI SSE] Event task_id mismatch via Predis: expected {$taskId}, got {$eventTaskId}");
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем не отключился ли клиент
|
||||
if (connection_aborted()) {
|
||||
$pubsub->unsubscribe($channel);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("[AI SSE] Error: " . $e->getMessage());
|
||||
sendSSE('error', ['message' => 'Redis error: ' . $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
|
||||
107
aiassist/check_redis_response.php
Normal file
107
aiassist/check_redis_response.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/**
|
||||
* check_redis_response.php
|
||||
* Проверка ответа в Redis (fallback если SSE не работает)
|
||||
* Проверяет ключ Redis где хранится последний ответ
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
$taskId = $_GET['task_id'] ?? null;
|
||||
|
||||
if (!$taskId) {
|
||||
throw new Exception('Missing task_id parameter');
|
||||
}
|
||||
|
||||
$redisKey = "ai:response:cache:{$taskId}";
|
||||
|
||||
// Подключаемся к Redis
|
||||
if (class_exists('Redis')) {
|
||||
$redis = new Redis();
|
||||
if (!$redis->connect('crm.clientright.ru', 6379)) {
|
||||
throw new Exception('Redis connection failed');
|
||||
}
|
||||
$redis->auth('CRM_Redis_Pass_2025_Secure!');
|
||||
|
||||
// Пробуем получить ответ из кеша
|
||||
$cachedResponse = $redis->get($redisKey);
|
||||
|
||||
if ($cachedResponse) {
|
||||
$responseData = json_decode($cachedResponse, true);
|
||||
|
||||
if ($responseData && isset($responseData['response'])) {
|
||||
echo json_encode([
|
||||
'found' => true,
|
||||
'response' => $responseData['response'],
|
||||
'status' => $responseData['status'] ?? 'completed',
|
||||
'timestamp' => $responseData['timestamp'] ?? null
|
||||
]);
|
||||
} else {
|
||||
// Если это просто строка
|
||||
echo json_encode([
|
||||
'found' => true,
|
||||
'response' => $cachedResponse
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
echo json_encode([
|
||||
'found' => false,
|
||||
'message' => 'Ответ еще не готов или истек TTL'
|
||||
]);
|
||||
}
|
||||
|
||||
$redis->close();
|
||||
} else {
|
||||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/vendor/autoload.php';
|
||||
|
||||
$redis = new Predis\Client([
|
||||
'scheme' => 'tcp',
|
||||
'host' => 'crm.clientright.ru',
|
||||
'port' => 6379,
|
||||
'password' => 'CRM_Redis_Pass_2025_Secure!',
|
||||
]);
|
||||
|
||||
$cachedResponse = $redis->get($redisKey);
|
||||
|
||||
if ($cachedResponse) {
|
||||
$responseData = json_decode($cachedResponse, true);
|
||||
|
||||
if ($responseData && isset($responseData['response'])) {
|
||||
echo json_encode([
|
||||
'found' => true,
|
||||
'response' => $responseData['response'],
|
||||
'status' => $responseData['status'] ?? 'completed',
|
||||
'timestamp' => $responseData['timestamp'] ?? null
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'found' => true,
|
||||
'response' => $cachedResponse
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
echo json_encode([
|
||||
'found' => false,
|
||||
'message' => 'Ответ еще не готов или истек TTL'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'found' => false,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
// aiassist/n8n_proxy.php
|
||||
// Proxy между AI Drawer и n8n с асинхронной обработкой
|
||||
// Proxy между AI Drawer и n8n
|
||||
// n8n обрабатывает запрос и публикует ответ напрямую в Redis
|
||||
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
@@ -22,17 +23,6 @@ if (!function_exists('curl_init')) {
|
||||
}
|
||||
|
||||
try {
|
||||
// Подключаемся к БД
|
||||
include_once('../config.inc.php');
|
||||
|
||||
$conn = new mysqli($dbconfig['db_server'], $dbconfig['db_username'], $dbconfig['db_password'], $dbconfig['db_name']);
|
||||
|
||||
if ($conn->connect_error) {
|
||||
throw new Exception("DB connection failed: " . $conn->connect_error);
|
||||
}
|
||||
|
||||
$conn->set_charset('utf8mb4');
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$input) {
|
||||
@@ -48,22 +38,8 @@ try {
|
||||
|
||||
error_log("N8N Proxy: New task {$taskId} for session {$sessionId}");
|
||||
|
||||
// Сохраняем начальный статус в БД
|
||||
$requestData = json_encode(['message' => $message, 'context' => $context], JSON_UNESCAPED_UNICODE);
|
||||
$stmt = $conn->prepare("INSERT INTO ai_responses (task_id, session_id, status, request_data) VALUES (?, ?, 'processing', ?)");
|
||||
$stmt->bind_param('sss', $taskId, $sessionId, $requestData);
|
||||
|
||||
if (!$stmt->execute()) {
|
||||
throw new Exception("Failed to save task: " . $stmt->error);
|
||||
}
|
||||
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
|
||||
// Формируем callback URL
|
||||
$callbackUrl = 'https://crm.clientright.ru/callback_ai_response.php';
|
||||
|
||||
// Отправляем запрос в n8n
|
||||
// n8n обработает и опубликует ответ напрямую в Redis: ai:response:{taskId}
|
||||
$n8nWebhookUrl = 'https://n8n.clientright.pro/webhook/0b20bf1e-7cda-4dc8-899e-a7c3be4096c0';
|
||||
|
||||
$payload = [
|
||||
@@ -71,12 +47,15 @@ try {
|
||||
'context' => $context,
|
||||
'sessionId' => $sessionId,
|
||||
'taskId' => $taskId,
|
||||
'callbackUrl' => $callbackUrl,
|
||||
'redisChannel' => "ai:response:{$taskId}", // Канал для публикации ответа
|
||||
'redisHost' => 'crm.clientright.ru',
|
||||
'redisPort' => 6379,
|
||||
'redisPassword' => 'CRM_Redis_Pass_2025_Secure!',
|
||||
'timestamp' => date('Y-m-d H:i:s'),
|
||||
'source' => 'crm-client'
|
||||
];
|
||||
|
||||
error_log("N8N Proxy: Sending to n8n - Task: {$taskId}");
|
||||
error_log("N8N Proxy: Sending to n8n - Task: {$taskId}, Redis channel: ai:response:{$taskId}");
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
@@ -105,11 +84,13 @@ try {
|
||||
}
|
||||
|
||||
// Возвращаем task_id клиенту
|
||||
// Клиент подпишется на SSE и получит ответ когда n8n опубликует в Redis
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'task_id' => $taskId,
|
||||
'status' => 'accepted',
|
||||
'message' => 'Запрос принят в обработку'
|
||||
'message' => 'Запрос принят в обработку',
|
||||
'redisChannel' => "ai:response:{$taskId}" // Для информации
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
|
||||
250
api_attach_documents.php
Normal file
250
api_attach_documents.php
Normal file
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
/**
|
||||
* API для привязки документов к проекту/заявке
|
||||
*
|
||||
* Использование из n8n:
|
||||
* POST https://crm.clientright.ru/api_attach_documents.php
|
||||
*
|
||||
* Входные данные (JSON массив):
|
||||
* [
|
||||
* {
|
||||
* "contact_id": "320096",
|
||||
* "project_id": "396868",
|
||||
* "ticket_id": "396936",
|
||||
* "filename": "boarding_pass.pdf",
|
||||
* "file_type": "flight_delay_boarding_or_ticket",
|
||||
* "file": "/bucket/path/to/file.pdf"
|
||||
* }
|
||||
* ]
|
||||
*/
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '0');
|
||||
|
||||
// Функция для логирования
|
||||
function log_message($message) {
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$line = "[$timestamp] $message\n";
|
||||
@file_put_contents(__DIR__ . '/logs/api_attach_documents.log', $line, FILE_APPEND | LOCK_EX);
|
||||
error_log('[api_attach_documents] ' . $message);
|
||||
}
|
||||
|
||||
// Функция для JSON ответа
|
||||
function json_response($data, $code = 200) {
|
||||
if (!headers_sent()) {
|
||||
http_response_code($code);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
}
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// CORS preflight
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
json_response(['status' => 'ok']);
|
||||
}
|
||||
|
||||
// Только POST
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
json_response(['success' => false, 'error' => 'Method not allowed'], 405);
|
||||
}
|
||||
|
||||
log_message('=== START API REQUEST ===');
|
||||
|
||||
// Получаем входные данные
|
||||
$input = file_get_contents('php://input');
|
||||
log_message('Raw input: ' . substr($input, 0, 500));
|
||||
|
||||
$input = ltrim($input, "\xEF\xBB\xBF\x00\x09\x0A\x0D\x20");
|
||||
$data = json_decode($input, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
log_message('❌ JSON Error: ' . json_last_error_msg());
|
||||
json_response([
|
||||
'success' => false,
|
||||
'error' => 'Invalid JSON: ' . json_last_error_msg()
|
||||
], 400);
|
||||
}
|
||||
|
||||
// Поддерживаем как массив, так и одиночный объект
|
||||
$documents_array = is_array($data) && isset($data[0]) ? $data : [$data];
|
||||
|
||||
log_message('Processing ' . count($documents_array) . ' document(s)');
|
||||
|
||||
// Обрабатываем каждый документ
|
||||
$processed_documents = [];
|
||||
$S3_HOST = 'https://s3.twcstorage.ru';
|
||||
|
||||
foreach ($documents_array as $idx => $doc) {
|
||||
$contact_id = $doc['contact_id'] ?? null;
|
||||
$project_id = $doc['project_id'] ?? null;
|
||||
$ticket_id = $doc['ticket_id'] ?? null;
|
||||
|
||||
// Поддерживаем оба формата: file и file_url
|
||||
$file_path = $doc['file'] ?? $doc['file_url'] ?? null;
|
||||
|
||||
if (!$file_path) {
|
||||
log_message("❌ Document #{$idx}: missing 'file' or 'file_url'");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Строим полный S3 URL
|
||||
if (strpos($file_path, 'http') === 0) {
|
||||
$file_url = $file_path;
|
||||
} elseif (strpos($file_path, '/') === 0) {
|
||||
$file_url = $S3_HOST . $file_path;
|
||||
} else {
|
||||
$file_url = $S3_HOST . '/' . $file_path;
|
||||
}
|
||||
|
||||
// Поддерживаем оба формата: filename и file_name
|
||||
$file_name = $doc['filename'] ?? $doc['file_name'] ?? null;
|
||||
|
||||
if (!$file_name) {
|
||||
log_message("❌ Document #{$idx}: missing 'filename' or 'file_name'");
|
||||
continue;
|
||||
}
|
||||
|
||||
$file_type = $doc['file_type'] ?? 'Документ';
|
||||
|
||||
// Валидация обязательных полей
|
||||
if (!$contact_id || !$project_id) {
|
||||
log_message("❌ Document #{$idx}: missing contact_id or project_id");
|
||||
continue;
|
||||
}
|
||||
|
||||
log_message(" [{$idx}] {$file_name} (type: {$file_type})");
|
||||
log_message(" Contact: {$contact_id}, Project: {$project_id}, Ticket: " . ($ticket_id ?: 'N/A'));
|
||||
log_message(" File URL: {$file_url}");
|
||||
|
||||
$processed_documents[] = [
|
||||
'url' => $file_url,
|
||||
'file_name' => $file_name,
|
||||
'description' => $file_type,
|
||||
'projectid' => (int)$project_id,
|
||||
'ticket_id' => $ticket_id ? (int)$ticket_id : null,
|
||||
'contactid' => (int)$contact_id,
|
||||
'pages' => 1
|
||||
];
|
||||
}
|
||||
|
||||
if (empty($processed_documents)) {
|
||||
log_message('❌ No valid documents to process');
|
||||
json_response([
|
||||
'success' => false,
|
||||
'error' => 'No valid documents to process'
|
||||
], 400);
|
||||
}
|
||||
|
||||
log_message('📤 Sending ' . count($processed_documents) . ' documents to upload_documents_to_crm.php');
|
||||
|
||||
// Формируем запрос к upload_documents_to_crm.php
|
||||
$upload_url = 'https://crm.clientright.ru/upload_documents_to_crm.php';
|
||||
|
||||
// Берем общие параметры из первого документа
|
||||
$first_doc = $processed_documents[0];
|
||||
|
||||
$payload = json_encode([
|
||||
'documents' => $processed_documents,
|
||||
'projectid' => $first_doc['projectid'],
|
||||
'ticket_id' => $first_doc['ticket_id'],
|
||||
'user_id' => 1
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
log_message('Payload: ' . substr($payload, 0, 500));
|
||||
|
||||
// Отправляем запрос
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $upload_url);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json',
|
||||
'Content-Length: ' . strlen($payload)
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($response === false) {
|
||||
log_message('❌ CURL error: ' . curl_error($ch));
|
||||
json_response([
|
||||
'success' => false,
|
||||
'error' => 'Internal error: ' . curl_error($ch)
|
||||
], 500);
|
||||
}
|
||||
|
||||
log_message("Response HTTP code: {$http_code}");
|
||||
log_message("Response: " . substr($response, 0, 500));
|
||||
|
||||
// Парсим ответ
|
||||
$result = json_decode($response, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
log_message('❌ Failed to parse response JSON: ' . json_last_error_msg());
|
||||
json_response([
|
||||
'success' => false,
|
||||
'error' => 'Invalid response from upload service'
|
||||
], 500);
|
||||
}
|
||||
|
||||
// Проверяем успешность
|
||||
if ($result && $result['success'] && isset($result['results'])) {
|
||||
$results_array = $result['results'];
|
||||
|
||||
// Формируем ответ
|
||||
$processed_results = [];
|
||||
$errors = [];
|
||||
|
||||
foreach ($results_array as $idx => $res) {
|
||||
if ($res['status'] === 'success') {
|
||||
$crm_result = $res['crm_result'] ?? [];
|
||||
|
||||
$processed_results[] = [
|
||||
'document_id' => $crm_result['document_id'] ?? null,
|
||||
'document_numeric_id' => $crm_result['document_numeric_id'] ?? null,
|
||||
'attached_to' => isset($res['ticket_id']) && $res['ticket_id'] ? 'ticket' : 'project',
|
||||
'attached_to_id' => $res['ticket_id'] ?? $res['projectid'] ?? null,
|
||||
'file_name' => $res['file_name'] ?? null,
|
||||
'file_type' => $res['description'] ?? null,
|
||||
's3_bucket' => $crm_result['s3_bucket'] ?? null,
|
||||
's3_key' => $crm_result['s3_key'] ?? null,
|
||||
'file_size' => $crm_result['file_size'] ?? null,
|
||||
'message' => $crm_result['message'] ?? null
|
||||
];
|
||||
|
||||
log_message(" ✅ [{$idx}] {$res['file_name']} → {$crm_result['document_id']}");
|
||||
} else {
|
||||
$error_msg = $res['crm_result']['message'] ?? 'Unknown error';
|
||||
$errors[] = [
|
||||
'file_name' => $res['file_name'] ?? 'Unknown',
|
||||
'error' => $error_msg
|
||||
];
|
||||
log_message(" ❌ [{$idx}] {$res['file_name']}: {$error_msg}");
|
||||
}
|
||||
}
|
||||
|
||||
log_message('✅ Success: ' . count($processed_results) . ' documents attached');
|
||||
|
||||
json_response([
|
||||
'success' => true,
|
||||
'total_processed' => count($results_array),
|
||||
'successful' => count($processed_results),
|
||||
'failed' => count($errors),
|
||||
'results' => $processed_results,
|
||||
'errors' => !empty($errors) ? $errors : null
|
||||
]);
|
||||
} else {
|
||||
log_message('❌ Upload failed: ' . ($result['error']['message'] ?? 'Unknown error'));
|
||||
json_response([
|
||||
'success' => false,
|
||||
'error' => $result['error']['message'] ?? 'Upload failed'
|
||||
], 500);
|
||||
}
|
||||
|
||||
3
crm_extensions/file_storage/.env.s3monitor
Normal file
3
crm_extensions/file_storage/.env.s3monitor
Normal file
@@ -0,0 +1,3 @@
|
||||
S3_ACCESS_KEY=2OMAK5ZNM900TAXM16J7
|
||||
S3_SECRET_KEY=f4ADllb5VZBAt2HdsyB8WcwVEU7U74MwFCa1DARG
|
||||
S3_BUCKET=f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c
|
||||
277
crm_extensions/file_storage/ARCHITECTURE.md
Normal file
277
crm_extensions/file_storage/ARCHITECTURE.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# 🏗️ Архитектура системы мониторинга файлов
|
||||
|
||||
## 🎯 Общая схема
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ ИСТОЧНИКИ ФАЙЛОВ │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │
|
||||
│ │ CRM PHP │ │ Nextcloud │ │ S3 Browser │ │ Внешние │ │
|
||||
│ │ │ │ WebUI │ │ Cyberduck │ │ скрипты │ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └─────┬──────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ └─────────────────┼─────────────────┼─────────────────┘ │
|
||||
│ │ │ │
|
||||
└───────────────────────────┼─────────────────┼───────────────────────────┘
|
||||
▼ ▼
|
||||
┌──────────────────────────────────────┐
|
||||
│ │
|
||||
│ TWC S3 Storage (Cloud) │
|
||||
│ f9825c87-18698658-c378-... │
|
||||
│ │
|
||||
└────┬─────────────────────────────┬───┘
|
||||
│ │
|
||||
│ (mounted via │ (polling)
|
||||
│ external │
|
||||
│ storage) │
|
||||
▼ ▼
|
||||
┌──────────────────────┐ ┌──────────────────────────┐
|
||||
│ Nextcloud │ │ n8n S3 Monitor │
|
||||
│ Docker Container │ │ Workflow │
|
||||
│ │ │ │
|
||||
│ Files change → │ │ Every 30s: │
|
||||
│ notify_storage_ │ │ 1. List S3 files │
|
||||
│ update (Redis) │ │ 2. Compare with prev │
|
||||
└──────────┬───────────┘ │ 3. Detect changes │
|
||||
│ └─────────┬────────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────────────┐ ┌─────────────────────────┐
|
||||
│ redis_bridge.js │ │ │
|
||||
│ (Node.js) │ │ (n8n internal) │
|
||||
│ │ │ │
|
||||
│ 1. Subscribe NC │ │ │
|
||||
│ Redis │ │ │
|
||||
│ 2. Parse events │ │ │
|
||||
│ 3. Enrich data │ │ │
|
||||
└──────────┬───────────┘ └─────────┬───────────────┘
|
||||
│ │
|
||||
│ │
|
||||
└────────────┬───────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────┐
|
||||
│ CRM Redis │
|
||||
│ 147.45.146.17:6379 │
|
||||
│ │
|
||||
│ Channel: │
|
||||
│ crm:file:events │
|
||||
└───────────┬──────────────────┘
|
||||
│
|
||||
│ (subscribe)
|
||||
│
|
||||
┌─────────────────┼─────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌────────────┐ ┌────────────┐ ┌────────────┐
|
||||
│ n8n │ │ CRM PHP │ │ Другие │
|
||||
│ Workflows │ │ Listeners │ │ сервисы │
|
||||
└────────────┘ └────────────┘ └────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Потоки событий
|
||||
|
||||
### Поток 1: Файлы через Nextcloud WebUI
|
||||
```
|
||||
User uploads file → Nextcloud → S3 Storage →
|
||||
→ Nextcloud detects change → notify_storage_update (NC Redis) →
|
||||
→ redis_bridge.js → crm:file:events (CRM Redis) → Обработчики
|
||||
```
|
||||
|
||||
⏱️ **Задержка:** ~1-2 секунды (реал-тайм)
|
||||
|
||||
---
|
||||
|
||||
### Поток 2: Файлы напрямую в S3
|
||||
```
|
||||
External tool → S3 Storage →
|
||||
→ n8n S3 Monitor (polling every 30s) → Detects change →
|
||||
→ crm:file:events (CRM Redis) → Обработчики
|
||||
```
|
||||
|
||||
⏱️ **Задержка:** 0-30 секунд (зависит от интервала polling)
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Компоненты системы
|
||||
|
||||
### 1. **redis_bridge.js** (Node.js, работает на хосте)
|
||||
- **Задача:** Мост между Nextcloud Redis и CRM Redis
|
||||
- **Вход:** `notify_storage_update` (Nextcloud Redis, port 6380)
|
||||
- **Выход:** `crm:file:events` (CRM Redis, port 6379)
|
||||
- **Статус:** ✅ Работает
|
||||
- **Запуск:**
|
||||
```bash
|
||||
cd /var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/file_storage
|
||||
/usr/bin/nodejs redis_bridge.js &
|
||||
```
|
||||
|
||||
### 2. **n8n S3 Monitor Workflow** (в n8n)
|
||||
- **Задача:** Мониторинг прямых изменений в S3
|
||||
- **Метод:** Polling (каждые 30 секунд)
|
||||
- **Вход:** TWC S3 API (ListObjectsV2)
|
||||
- **Выход:** `crm:file:events` (CRM Redis)
|
||||
- **Статус:** ⚙️ Нужно настроить
|
||||
- **Файл:** `n8n_s3_monitor_workflow.json`
|
||||
|
||||
### 3. **CRM Redis** (центральная шина событий)
|
||||
- **Адрес:** `147.45.146.17:6379`
|
||||
- **Канал:** `crm:file:events`
|
||||
- **Формат события:**
|
||||
```json
|
||||
{
|
||||
"type": "file_created|file_modified|file_deleted",
|
||||
"source": "nextcloud|s3_monitor|crm",
|
||||
"timestamp": "2025-10-30T12:34:56Z",
|
||||
"path": "full/path/to/file.txt",
|
||||
"filename": "file.txt",
|
||||
"size": 12345,
|
||||
"file_id": 73460,
|
||||
"etag": "abc123...",
|
||||
"operation": "create|update|delete"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Сценарии использования
|
||||
|
||||
### Сценарий 1: Обработка ERV файлов
|
||||
```
|
||||
1. Загружаешь ERV файл в S3 через S3 Browser
|
||||
2. n8n S3 Monitor обнаруживает файл (через 0-30с)
|
||||
3. Событие публикуется в Redis → crm:file:events
|
||||
4. n8n ERV Processor подхватывает событие
|
||||
5. Скачивает файл из S3
|
||||
6. Обрабатывает ERV
|
||||
7. Загружает в CRM
|
||||
8. Отправляет уведомление
|
||||
```
|
||||
|
||||
### Сценарий 2: Автоматическая обработка PDF счетов
|
||||
```
|
||||
1. Клиент загружает PDF в Nextcloud
|
||||
2. Nextcloud → notify_storage_update → redis_bridge.js
|
||||
3. Событие в Redis (реал-тайм, ~1с)
|
||||
4. n8n Invoice Processor:
|
||||
- Распознаёт текст (OCR)
|
||||
- Извлекает данные
|
||||
- Создаёт счёт в CRM
|
||||
- Уведомляет бухгалтера
|
||||
```
|
||||
|
||||
### Сценарий 3: Синхронизация с внешней системой
|
||||
```
|
||||
1. Внешний скрипт пишет в S3
|
||||
2. n8n S3 Monitor (через 30с)
|
||||
3. Redis событие
|
||||
4. n8n External Sync:
|
||||
- Проверяет формат файла
|
||||
- Отправляет в external API
|
||||
- Логирует в CRM
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Настройки производительности
|
||||
|
||||
### Интервал polling S3 Monitor
|
||||
|
||||
| Интервал | Задержка | Нагрузка на API | Рекомендация |
|
||||
|----------|----------|-----------------|--------------|
|
||||
| 10 сек | 0-10с | Высокая | Только для критичных файлов |
|
||||
| 30 сек | 0-30с | Средняя | ✅ **Оптимально** |
|
||||
| 60 сек | 0-60с | Низкая | Для некритичных файлов |
|
||||
| 300 сек | 0-5мин | Минимальная | Для архивов |
|
||||
|
||||
### Количество файлов
|
||||
|
||||
- **< 1000 файлов:** Мониторь весь bucket
|
||||
- **1000-10000:** Используй `Prefix` для фильтрации папок
|
||||
- **> 10000:** Создай отдельные workflows для разных папок
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Мониторинг и логи
|
||||
|
||||
### Проверка работы redis_bridge.js
|
||||
```bash
|
||||
ps aux | grep redis_bridge.js
|
||||
tail -f /var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/file_storage/redis_bridge.log
|
||||
```
|
||||
|
||||
### Проверка событий в Redis
|
||||
```bash
|
||||
redis-cli -h 147.45.146.17 -p 6379 -a 'CRM_Redis_Pass_2025_Secure!' \
|
||||
SUBSCRIBE crm:file:events
|
||||
```
|
||||
|
||||
### Проверка n8n workflow
|
||||
- n8n UI → Workflows → S3 File Monitor → Executions
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Расширение системы
|
||||
|
||||
### Добавление новых источников событий
|
||||
|
||||
Любой компонент может публиковать в `crm:file:events`:
|
||||
|
||||
**Пример из PHP (CRM):**
|
||||
```php
|
||||
$redis = new Redis();
|
||||
$redis->connect('147.45.146.17', 6379);
|
||||
$redis->auth('CRM_Redis_Pass_2025_Secure!');
|
||||
|
||||
$event = [
|
||||
'type' => 'file_created',
|
||||
'source' => 'crm_api',
|
||||
'timestamp' => date('c'),
|
||||
'path' => $filePath,
|
||||
'filename' => basename($filePath),
|
||||
'size' => filesize($localPath),
|
||||
'user_id' => $userId
|
||||
];
|
||||
|
||||
$redis->publish('crm:file:events', json_encode($event));
|
||||
```
|
||||
|
||||
**Пример из Python:**
|
||||
```python
|
||||
import redis
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
r = redis.Redis(
|
||||
host='147.45.146.17',
|
||||
port=6379,
|
||||
password='CRM_Redis_Pass_2025_Secure!'
|
||||
)
|
||||
|
||||
event = {
|
||||
'type': 'file_created',
|
||||
'source': 'python_script',
|
||||
'timestamp': datetime.utcnow().isoformat() + 'Z',
|
||||
'path': file_path,
|
||||
'filename': os.path.basename(file_path)
|
||||
}
|
||||
|
||||
r.publish('crm:file:events', json.dumps(event))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Итого
|
||||
|
||||
✅ **Nextcloud файлы** → реал-тайм через `redis_bridge.js`
|
||||
✅ **S3 прямые загрузки** → polling через n8n (0-30с задержка)
|
||||
✅ **CRM файлы** → прямая публикация в Redis
|
||||
✅ **Единая точка обработки** → Redis канал `crm:file:events`
|
||||
✅ **Гибкость** → любой сервис может подписаться и обработать
|
||||
|
||||
**Всё работает через один канал Redis! 🎉**
|
||||
|
||||
238
crm_extensions/file_storage/FINAL_SETUP_SUMMARY.md
Normal file
238
crm_extensions/file_storage/FINAL_SETUP_SUMMARY.md
Normal file
@@ -0,0 +1,238 @@
|
||||
# 🎉 ФИНАЛЬНАЯ СВОДКА - S3 МОНИТОРИНГ
|
||||
|
||||
## ✅ ЧТО РАБОТАЕТ:
|
||||
|
||||
### 1️⃣ **S3 Monitor** (Docker контейнер)
|
||||
```
|
||||
Контейнер: s3-monitor
|
||||
Статус: ✅ Работает
|
||||
Bucket: f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c
|
||||
Файлов: 24,523
|
||||
Интервал: каждые 30 секунд
|
||||
Автоперезапуск: ✅
|
||||
```
|
||||
|
||||
**Что делает:**
|
||||
- Каждые 30 секунд сканирует S3 bucket
|
||||
- Получает **ВСЕ** файлы (24,523 штук через pagination)
|
||||
- Сравнивает с предыдущим состоянием
|
||||
- Публикует изменения в Redis
|
||||
|
||||
**Команды:**
|
||||
```bash
|
||||
docker logs s3-monitor -f # Логи в реальном времени
|
||||
docker stop s3-monitor # Остановить
|
||||
docker start s3-monitor # Запустить
|
||||
docker restart s3-monitor # Перезапустить
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣ **Redis Bridge** (Nextcloud → CRM Redis)
|
||||
```
|
||||
Процесс: /usr/bin/nodejs redis_bridge.js
|
||||
Статус: ✅ Работает
|
||||
Порт NC Redis: 127.0.0.1:6380
|
||||
Порт CRM Redis: 147.45.146.17:6379
|
||||
```
|
||||
|
||||
**Что делает:**
|
||||
- Слушает Nextcloud Redis канал `notify_storage_update`
|
||||
- Фильтрует временные файлы (.part, .lock, cache)
|
||||
- **Дедупликация** - один file_id = одно событие (в течение 5 сек)
|
||||
- Публикует в CRM Redis канал `crm:file:events`
|
||||
|
||||
**Команды:**
|
||||
```bash
|
||||
tail -f redis_bridge.log # Логи
|
||||
ps aux | grep redis_bridge # Статус
|
||||
pkill -f redis_bridge.js # Остановить
|
||||
nohup /usr/bin/nodejs redis_bridge.js > redis_bridge.log 2>&1 & # Запустить
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3️⃣ **Redis Канал** (централизованная шина событий)
|
||||
```
|
||||
Host: 147.45.146.17
|
||||
Port: 6379
|
||||
Password: CRM_Redis_Pass_2025_Secure!
|
||||
Channel: crm:file:events
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 ФОРМАТ СОБЫТИЙ:
|
||||
|
||||
### От S3 Monitor:
|
||||
```json
|
||||
{
|
||||
"type": "file_created",
|
||||
"source": "s3_monitor",
|
||||
"timestamp": "2025-10-30T20:49:31.593Z",
|
||||
"path": "folder/subfolder/file.xlsx",
|
||||
"filename": "file.xlsx",
|
||||
"action": "created",
|
||||
"size": 8224,
|
||||
"etag": "7004954627252c9d0a7e6417f8325d07",
|
||||
"last_modified": "2025-10-30T20:49:14.132Z"
|
||||
}
|
||||
```
|
||||
|
||||
### От Nextcloud (redis_bridge):
|
||||
```json
|
||||
{
|
||||
"type": "file_update",
|
||||
"source": "nextcloud",
|
||||
"timestamp": "2025-10-30T20:52:21.236Z",
|
||||
"storage_id": 4,
|
||||
"path": "crm2/CRM_Active_Files/file.pdf",
|
||||
"file_id": 42594,
|
||||
"filename": "file.pdf",
|
||||
"operation": "update"
|
||||
}
|
||||
```
|
||||
|
||||
**Оба публикуются в один канал:** `crm:file:events`
|
||||
|
||||
---
|
||||
|
||||
## 🎯 КАК ОБРАБАТЫВАТЬ В N8N:
|
||||
|
||||
### Шаг 1: Импортируй workflow обработчик
|
||||
Файл: `n8n_s3_event_processor.json`
|
||||
|
||||
### Шаг 2: Настрой Redis Trigger
|
||||
```
|
||||
Node: Redis Subscribe
|
||||
Channel: crm:file:events
|
||||
Credential: CRM Redis
|
||||
```
|
||||
|
||||
### Шаг 3: Parse JSON (если нужно)
|
||||
```javascript
|
||||
// n8n автоматически парсит, но если нужно:
|
||||
const message = $json.message;
|
||||
const event = typeof message === 'string' ? JSON.parse(message) : message;
|
||||
return [{ json: event }];
|
||||
```
|
||||
|
||||
### Шаг 4: Фильтруй по типу
|
||||
```javascript
|
||||
// Только новые файлы
|
||||
$json.type === 'file_created'
|
||||
|
||||
// Только XLSX
|
||||
$json.filename.endsWith('.xlsx')
|
||||
|
||||
// Только из определённой папки
|
||||
$json.path.includes('Documents/')
|
||||
```
|
||||
|
||||
### Шаг 5: Обработай файл
|
||||
```
|
||||
Switch по расширению:
|
||||
├─ .xlsx → Обработка Excel
|
||||
├─ .pdf → OCR и извлечение данных
|
||||
├─ .jpg → Обработка изображений
|
||||
└─ Другие → Логирование
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 ПРОВЕРКА СОБЫТИЙ:
|
||||
|
||||
### Подпишись на Redis:
|
||||
```bash
|
||||
redis-cli -h 147.45.146.17 -p 6379 -a 'CRM_Redis_Pass_2025_Secure!' \
|
||||
SUBSCRIBE crm:file:events
|
||||
```
|
||||
|
||||
### Загрузи файл:
|
||||
- Через Nextcloud WebUI
|
||||
- Через S3 Browser
|
||||
- Через aws-cli
|
||||
|
||||
### Увидишь событие:
|
||||
```
|
||||
1) "message"
|
||||
2) "crm:file:events"
|
||||
3) "{\"type\":\"file_created\",\"filename\":\"test.pdf\",...}"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 ИСТОЧНИКИ СОБЫТИЙ:
|
||||
|
||||
| Источник | Метод | Задержка | Фильтры |
|
||||
|----------|-------|----------|---------|
|
||||
| **Nextcloud WebUI** | Redis Bridge | 1-2 сек | ✅ Дедупликация<br>✅ Фильтр .part<br>✅ Фильтр cache |
|
||||
| **S3 прямая загрузка** | S3 Monitor | 0-30 сек | Нет |
|
||||
| **CRM API** | Прямая публикация | 0 сек | Настраивается |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 ДОПОЛНИТЕЛЬНАЯ ФИЛЬТРАЦИЯ В N8N:
|
||||
|
||||
Если всё ещё приходит много событий от Nextcloud, добавь в n8n:
|
||||
|
||||
```javascript
|
||||
// Фильтр: пропускаем дубликаты по filename + timestamp
|
||||
const cache = $getWorkflowStaticData('node');
|
||||
cache.recent = cache.recent || [];
|
||||
|
||||
const key = `${$json.filename}_${$json.file_id}`;
|
||||
const now = Date.now();
|
||||
|
||||
// Очищаем старые (>10 секунд)
|
||||
cache.recent = cache.recent.filter(item => (now - item.time) < 10000);
|
||||
|
||||
// Проверяем дубликат
|
||||
if (cache.recent.find(item => item.key === key)) {
|
||||
return []; // Пропускаем дубликат
|
||||
}
|
||||
|
||||
// Добавляем в кеш
|
||||
cache.recent.push({ key, time: now });
|
||||
|
||||
return [$input.item];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 ФАЙЛЫ:
|
||||
|
||||
Все в:
|
||||
```
|
||||
/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/file_storage/
|
||||
```
|
||||
|
||||
**Рабочие скрипты:**
|
||||
- ✅ `s3_monitor_docker.js` - S3 мониторинг (запущен в Docker)
|
||||
- ✅ `redis_bridge.js` - Nextcloud→CRM мост (запущен)
|
||||
- ✅ `.env.s3monitor` - credentials для S3
|
||||
|
||||
**N8N Workflows для импорта:**
|
||||
- `n8n_s3_event_processor.json` - обработчик событий (НОВЫЙ!)
|
||||
- `n8n_nextcloud_activity_monitor.json` - альтернатива через Activity API
|
||||
- `n8n_s3_monitor_workflow.json` - альтернатива S3 Monitor (если не хочешь Docker)
|
||||
|
||||
**Документация:**
|
||||
- `SETUP_CHECKLIST.md` - чек-лист настройки
|
||||
- `S3_MONITORING_GUIDE.md` - руководство по S3
|
||||
- `ARCHITECTURE.md` - схема архитектуры
|
||||
- `NEXTCLOUD_API_OVERVIEW.md` - обзор Nextcloud API
|
||||
|
||||
---
|
||||
|
||||
## 🎉 ГОТОВО!
|
||||
|
||||
**Система полностью работает:**
|
||||
- ✅ S3 мониторинг (24,523 файла)
|
||||
- ✅ Nextcloud мониторинг (с дедупликацией)
|
||||
- ✅ Redis публикация
|
||||
- ✅ Автоперезапуск
|
||||
- ✅ Фильтрация дубликатов
|
||||
|
||||
**Теперь импортируй `n8n_s3_event_processor.json` в n8n и начинай обрабатывать файлы!** 🚀
|
||||
|
||||
377
crm_extensions/file_storage/NEXTCLOUD_API_OVERVIEW.md
Normal file
377
crm_extensions/file_storage/NEXTCLOUD_API_OVERVIEW.md
Normal file
@@ -0,0 +1,377 @@
|
||||
# 🌐 Nextcloud API - Обзор и возможности
|
||||
|
||||
## 📋 Доступные API
|
||||
|
||||
### 1. **WebDAV API** ✅ (РАБОТАЕТ)
|
||||
**Базовый URL:** `https://office.clientright.ru:8443/remote.php/webdav/`
|
||||
|
||||
**Аутентификация:** Basic Auth (admin + app password)
|
||||
|
||||
**Возможности:**
|
||||
- ✅ Список файлов и папок (PROPFIND)
|
||||
- ✅ Загрузка файлов (PUT)
|
||||
- ✅ Скачивание файлов (GET)
|
||||
- ✅ Удаление файлов (DELETE)
|
||||
- ✅ Создание папок (MKCOL)
|
||||
- ✅ Перемещение/копирование (MOVE/COPY)
|
||||
- ✅ Получение метаданных (PROPFIND)
|
||||
|
||||
**Пример - список файлов:**
|
||||
```bash
|
||||
curl -u "admin:APP_PASSWORD" \
|
||||
-X PROPFIND \
|
||||
"https://office.clientright.ru:8443/remote.php/webdav/" \
|
||||
-H "Depth: 1"
|
||||
```
|
||||
|
||||
**Пример - загрузка файла:**
|
||||
```bash
|
||||
curl -u "admin:APP_PASSWORD" \
|
||||
-T /path/to/local/file.txt \
|
||||
"https://office.clientright.ru:8443/remote.php/webdav/file.txt"
|
||||
```
|
||||
|
||||
**Пример - скачивание файла:**
|
||||
```bash
|
||||
curl -u "admin:APP_PASSWORD" \
|
||||
"https://office.clientright.ru:8443/remote.php/webdav/file.txt" \
|
||||
-o downloaded.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. **OCS API (Open Collaboration Services)** ✅
|
||||
|
||||
**Базовый URL:** `https://office.clientright.ru:8443/ocs/v2.php/`
|
||||
|
||||
**Заголовки:** `OCS-APIRequest: true`
|
||||
|
||||
**Формат ответа:** JSON (`?format=json`)
|
||||
|
||||
#### 2.1 **Capabilities API** (информация о сервере)
|
||||
```bash
|
||||
curl -u "admin:APP_PASSWORD" \
|
||||
"https://office.clientright.ru:8443/ocs/v1.php/cloud/capabilities?format=json" \
|
||||
-H "OCS-APIRequest: true"
|
||||
```
|
||||
|
||||
**Ответ включает:**
|
||||
- Версию Nextcloud (31.0.9)
|
||||
- Доступные возможности
|
||||
- Лимиты загрузки
|
||||
- Настройки шаринга
|
||||
|
||||
---
|
||||
|
||||
#### 2.2 **Activity API** ✅ (СОБЫТИЯ ФАЙЛОВ!)
|
||||
|
||||
**URL:** `https://office.clientright.ru:8443/ocs/v2.php/apps/activity/api/v2/activity`
|
||||
|
||||
**Параметры:**
|
||||
- `format=json` - формат ответа
|
||||
- `limit=N` - количество событий
|
||||
- `since=TIMESTAMP` - события после определённой даты
|
||||
|
||||
**Пример:**
|
||||
```bash
|
||||
curl -u "admin:APP_PASSWORD" \
|
||||
"https://office.clientright.ru:8443/ocs/v2.php/apps/activity/api/v2/activity?format=json&limit=10" \
|
||||
-H "OCS-APIRequest: true"
|
||||
```
|
||||
|
||||
**Типы событий:**
|
||||
- `file_created` - файл создан
|
||||
- `file_changed` - файл изменён
|
||||
- `file_deleted` - файл удалён
|
||||
- `file_restored` - файл восстановлен
|
||||
- `file_shared` - файл расшарен
|
||||
|
||||
**Структура события:**
|
||||
```json
|
||||
{
|
||||
"activity_id": 195,
|
||||
"type": "file_created",
|
||||
"subject": "Вы создали «experimental_report.xlsx»",
|
||||
"message": "",
|
||||
"object_type": "files",
|
||||
"object_id": 73460,
|
||||
"object_name": "/experimental_report.xlsx",
|
||||
"datetime": "2025-10-30T12:53:40+00:00",
|
||||
"user": "admin"
|
||||
}
|
||||
```
|
||||
|
||||
**⚠️ Ограничения:**
|
||||
- Нет real-time уведомлений (только polling)
|
||||
- События агрегируются (несколько файлов в одном событии)
|
||||
- Может быть задержка до минуты
|
||||
|
||||
---
|
||||
|
||||
#### 2.3 **Users API**
|
||||
|
||||
**Список пользователей:**
|
||||
```bash
|
||||
curl -u "admin:APP_PASSWORD" \
|
||||
"https://office.clientright.ru:8443/ocs/v1.php/cloud/users?format=json" \
|
||||
-H "OCS-APIRequest: true"
|
||||
```
|
||||
|
||||
**Информация о пользователе:**
|
||||
```bash
|
||||
curl -u "admin:APP_PASSWORD" \
|
||||
"https://office.clientright.ru:8443/ocs/v1.php/cloud/users/admin?format=json" \
|
||||
-H "OCS-APIRequest: true"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 2.4 **Sharing API**
|
||||
|
||||
**Список расшаренных файлов:**
|
||||
```bash
|
||||
curl -u "admin:APP_PASSWORD" \
|
||||
"https://office.clientright.ru:8443/ocs/v2.php/apps/files_sharing/api/v1/shares?format=json" \
|
||||
-H "OCS-APIRequest: true"
|
||||
```
|
||||
|
||||
**Создать публичную ссылку:**
|
||||
```bash
|
||||
curl -u "admin:APP_PASSWORD" \
|
||||
-X POST \
|
||||
-d "path=/file.txt&shareType=3" \
|
||||
"https://office.clientright.ru:8443/ocs/v2.php/apps/files_sharing/api/v1/shares?format=json" \
|
||||
-H "OCS-APIRequest: true"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. **Direct Download URL**
|
||||
|
||||
Для файлов можно получить прямую ссылку скачивания:
|
||||
|
||||
**Формат:**
|
||||
```
|
||||
https://office.clientright.ru:8443/index.php/apps/files/ajax/download.php?dir=/&files=filename.txt
|
||||
```
|
||||
|
||||
Или через WebDAV:
|
||||
```
|
||||
https://office.clientright.ru:8443/remote.php/webdav/filename.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Использование для мониторинга файлов
|
||||
|
||||
### Вариант 1: Activity API Polling (РЕКОМЕНДУЮ для Nextcloud)
|
||||
|
||||
**Создай n8n workflow:**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Schedule (каждые 30 сек) │
|
||||
└─────────────────┬───────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ HTTP Request │
|
||||
│ GET /ocs/v2.php/apps/activity/api/v2/ │
|
||||
│ activity?format=json&limit=100 │
|
||||
└─────────────────┬───────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Code (JS) │
|
||||
│ - Фильтруем file_created/changed/deleted │
|
||||
│ - Сохраняем последний activity_id │
|
||||
│ - Возвращаем только новые события │
|
||||
└─────────────────┬───────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Redis Publish │
|
||||
│ Channel: crm:file:events │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Преимущества:**
|
||||
- ✅ Официальный API
|
||||
- ✅ Надёжный
|
||||
- ✅ Детальная информация о событиях
|
||||
- ✅ Не требует доступа к Redis Nextcloud
|
||||
|
||||
**Недостатки:**
|
||||
- ⚠️ Задержка 30-60 сек (polling)
|
||||
- ⚠️ События агрегируются
|
||||
|
||||
---
|
||||
|
||||
### Вариант 2: Redis Bridge (УЖЕ РАБОТАЕТ!)
|
||||
|
||||
```
|
||||
Nextcloud Redis (notify_storage_update) →
|
||||
→ redis_bridge.js →
|
||||
→ CRM Redis (crm:file:events)
|
||||
```
|
||||
|
||||
**Преимущества:**
|
||||
- ✅ Real-time (~1-2 сек)
|
||||
- ✅ Не нагружает Nextcloud API
|
||||
|
||||
**Недостатки:**
|
||||
- ⚠️ Требует доступ к внутреннему Redis
|
||||
- ⚠️ Меньше метаданных
|
||||
|
||||
---
|
||||
|
||||
## 📊 Сравнение подходов
|
||||
|
||||
| Метод | Задержка | Детали | Сложность | Надёжность |
|
||||
|-------|----------|--------|-----------|------------|
|
||||
| **Activity API** | 30-60с | ⭐⭐⭐ | Низкая | ⭐⭐⭐ |
|
||||
| **Redis Bridge** | 1-2с | ⭐⭐ | Средняя | ⭐⭐⭐ |
|
||||
| **S3 Monitor** | 0-30с | ⭐⭐ | Низкая | ⭐⭐⭐ |
|
||||
| **WebDAV Poll** | 60с+ | ⭐ | Низкая | ⭐⭐ |
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Примеры кода
|
||||
|
||||
### PHP - Получение последних событий
|
||||
|
||||
```php
|
||||
<?php
|
||||
$username = 'admin';
|
||||
$password = 'APP_PASSWORD';
|
||||
$url = 'https://office.clientright.ru:8443/ocs/v2.php/apps/activity/api/v2/activity?format=json&limit=10';
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_USERPWD, "$username:$password");
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['OCS-APIRequest: true']);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$data = json_decode($response, true);
|
||||
|
||||
foreach ($data['ocs']['data'] as $activity) {
|
||||
if (in_array($activity['type'], ['file_created', 'file_changed', 'file_deleted'])) {
|
||||
echo "Event: {$activity['type']}\n";
|
||||
echo "File: {$activity['object_name']}\n";
|
||||
echo "Time: {$activity['datetime']}\n";
|
||||
echo "---\n";
|
||||
}
|
||||
}
|
||||
?>
|
||||
```
|
||||
|
||||
### Node.js - Activity API Monitor
|
||||
|
||||
```javascript
|
||||
const axios = require('axios');
|
||||
const Redis = require('ioredis');
|
||||
|
||||
const CONFIG = {
|
||||
nextcloud: {
|
||||
url: 'https://office.clientright.ru:8443',
|
||||
username: 'admin',
|
||||
password: 'APP_PASSWORD'
|
||||
},
|
||||
redis: {
|
||||
host: '147.45.146.17',
|
||||
port: 6379,
|
||||
password: 'CRM_Redis_Pass_2025_Secure!'
|
||||
},
|
||||
pollInterval: 30000 // 30 секунд
|
||||
};
|
||||
|
||||
const redis = new Redis(CONFIG.redis);
|
||||
let lastActivityId = 0;
|
||||
|
||||
async function checkActivities() {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${CONFIG.nextcloud.url}/ocs/v2.php/apps/activity/api/v2/activity`,
|
||||
{
|
||||
params: { format: 'json', limit: 100 },
|
||||
headers: { 'OCS-APIRequest': 'true' },
|
||||
auth: {
|
||||
username: CONFIG.nextcloud.username,
|
||||
password: CONFIG.nextcloud.password
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const activities = response.data.ocs.data;
|
||||
const fileEvents = activities.filter(a =>
|
||||
['file_created', 'file_changed', 'file_deleted'].includes(a.type) &&
|
||||
a.activity_id > lastActivityId
|
||||
);
|
||||
|
||||
for (const event of fileEvents.reverse()) {
|
||||
const payload = {
|
||||
type: event.type,
|
||||
source: 'nextcloud_activity',
|
||||
timestamp: event.datetime,
|
||||
file_id: event.object_id,
|
||||
path: event.object_name,
|
||||
filename: event.object_name.split('/').pop(),
|
||||
user: event.user
|
||||
};
|
||||
|
||||
await redis.publish('crm:file:events', JSON.stringify(payload));
|
||||
console.log(`📤 Published: ${event.type} - ${event.object_name}`);
|
||||
|
||||
lastActivityId = Math.max(lastActivityId, event.activity_id);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Запуск
|
||||
console.log('🚀 Nextcloud Activity Monitor');
|
||||
setInterval(checkActivities, CONFIG.pollInterval);
|
||||
checkActivities();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Рекомендации
|
||||
|
||||
### Для твоего use case:
|
||||
|
||||
**Комбинируй несколько подходов:**
|
||||
|
||||
1. **Nextcloud файлы (WebUI)** → **Redis Bridge** (real-time, 1-2с)
|
||||
2. **S3 прямые загрузки** → **n8n S3 Monitor** (polling, 0-30с)
|
||||
3. **CRM загрузки** → **Прямая публикация в Redis** (instant)
|
||||
|
||||
**Все события в одном канале:** `crm:file:events`
|
||||
|
||||
**Альтернатива (если нужна простота):**
|
||||
- Используй **только Activity API** через n8n для Nextcloud событий
|
||||
- Плюс **S3 Monitor** для прямых загрузок в S3
|
||||
|
||||
---
|
||||
|
||||
## 📚 Полезные ссылки
|
||||
|
||||
- [Nextcloud WebDAV Documentation](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/WebDAV/)
|
||||
- [Nextcloud OCS API](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/)
|
||||
- [Activity API](https://github.com/nextcloud/activity/blob/master/docs/endpoint-v2.md)
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Безопасность
|
||||
|
||||
**App Password:** `tGHKS-3cC9m-7Hggb-65Awk-zxWQE`
|
||||
**Пользователь:** `admin`
|
||||
|
||||
⚠️ **Важно:** Используй App Passwords, а не основной пароль!
|
||||
|
||||
---
|
||||
|
||||
**Готово! Теперь ты знаешь все доступные API Nextcloud! 🎉**
|
||||
|
||||
169
crm_extensions/file_storage/NEXTCLOUD_REDIS_CREDS.md
Normal file
169
crm_extensions/file_storage/NEXTCLOUD_REDIS_CREDS.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# 🔑 Nextcloud Redis - Креды и подключение
|
||||
|
||||
## 📋 Основная информация
|
||||
|
||||
**Контейнер:** `nextcloud-redis`
|
||||
**IP внутри Docker сети:** `172.24.0.4`
|
||||
**Порт:** `6379`
|
||||
**Пароль:** `НЕТ` (не установлен)
|
||||
|
||||
---
|
||||
|
||||
## 🔌 Варианты подключения
|
||||
|
||||
### **ВАРИАНТ 1: Из хоста (если проброшен порт)**
|
||||
|
||||
**Если пробросить порт наружу:**
|
||||
```bash
|
||||
# Перезапустить контейнер с проброшенным портом:
|
||||
docker stop nextcloud-redis
|
||||
docker start -p 6378:6379 nextcloud-redis
|
||||
```
|
||||
|
||||
**Тогда подключаться так:**
|
||||
```
|
||||
Host: localhost (или 147.45.146.17)
|
||||
Port: 6378
|
||||
Password: (пусто)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **ВАРИАНТ 2: Через Docker network IP**
|
||||
|
||||
**Из другого контейнера в той же сети:**
|
||||
```
|
||||
Host: 172.24.0.4
|
||||
Port: 6379
|
||||
Password: (пусто)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **ВАРИАНТ 3: Из самого хоста (если в той же сети)**
|
||||
|
||||
**Если хост в той же Docker сети:**
|
||||
```
|
||||
Host: 172.24.0.4
|
||||
Port: 6379
|
||||
Password: (пусто)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📡 Каналы Redis для подписки
|
||||
|
||||
### **Главный канал для файловых изменений:**
|
||||
```
|
||||
notify_storage_update
|
||||
```
|
||||
Этот канал публикует события когда:
|
||||
- Файл создан/удалён/переименован
|
||||
- Папка создана/удалена/переименована
|
||||
|
||||
### **Другие полезные каналы:**
|
||||
```
|
||||
notify_activity # Общая активность
|
||||
notify_notification # Уведомления пользователей
|
||||
notify_pre_auth # Пред-аутентификация
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Тест подключения
|
||||
|
||||
### **Из командной строки:**
|
||||
|
||||
```bash
|
||||
# Подключиться к Redis через docker exec:
|
||||
docker exec -it nextcloud-redis redis-cli
|
||||
|
||||
# Или если порт проброшен:
|
||||
redis-cli -h localhost -p 6378
|
||||
```
|
||||
|
||||
**Тест подписки на канал:**
|
||||
```redis
|
||||
SUBSCRIBE notify_storage_update
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Формат сообщений
|
||||
|
||||
Сообщения в канале `notify_storage_update` имеют формат:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "notify_storage_update",
|
||||
"path": "/admin/files/Documents/Projects/Проект_390983/файл_395695.docx",
|
||||
"user": "admin",
|
||||
"action": "write" | "delete" | "rename",
|
||||
"oldPath": "...", // только для rename
|
||||
"timestamp": "..."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Для n8n подключения
|
||||
|
||||
**Настройки в n8n:**
|
||||
```
|
||||
Host: 172.24.0.4 (или localhost:6378 если проброшен порт)
|
||||
Port: 6379 (или 6378 если проброшен)
|
||||
Password: (оставить пустым)
|
||||
Database: 0
|
||||
```
|
||||
|
||||
**Триггер:**
|
||||
- Использовать "Redis Trigger" ноду
|
||||
- Channel: `notify_storage_update`
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Для нашего Node.js listener
|
||||
|
||||
**Обновить `nextcloud_listener.js`:**
|
||||
```javascript
|
||||
const redis = new Redis({
|
||||
host: '172.24.0.4', // или localhost если проброшен порт
|
||||
port: 6379, // или 6378 если проброшен
|
||||
password: '' // пусто
|
||||
});
|
||||
|
||||
redis.subscribe('notify_storage_update');
|
||||
redis.on('message', (channel, message) => {
|
||||
const event = JSON.parse(message);
|
||||
// Обработка события
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ ВАЖНО
|
||||
|
||||
1. **Безопасность:** Redis БЕЗ пароля доступен только из Docker сети!
|
||||
2. **Если нужен доступ снаружи:** Обязательно установи пароль!
|
||||
3. **Мониторинг:** Следи за нагрузкой на Redis при подписке на каналы
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Рекомендация: Установить пароль
|
||||
|
||||
Если планируешь пробрасывать порт наружу:
|
||||
|
||||
```bash
|
||||
docker exec nextcloud-redis redis-cli CONFIG SET requirepass "ваш_пароль"
|
||||
```
|
||||
|
||||
И добавить в Nextcloud config.php:
|
||||
```php
|
||||
'redis' => array (
|
||||
'atype' => 'redis',
|
||||
'host' => 'nextcloud-redis',
|
||||
'port' => 6379,
|
||||
'password' => 'ваш_пароль',
|
||||
),
|
||||
```
|
||||
|
||||
182
crm_extensions/file_storage/PRODUCTION_READY.md
Normal file
182
crm_extensions/file_storage/PRODUCTION_READY.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# 🎉 PRODUCTION READY - Мониторинг файлов
|
||||
|
||||
## ✅ ЧТО РАБОТАЕТ:
|
||||
|
||||
### 1️⃣ Nextcloud Activity Monitor
|
||||
**Скрипт:** `nextcloud_activity_monitor.js`
|
||||
**Запущен:** ✅ (PID: 2122)
|
||||
**Лог:** `nextcloud_activity.log`
|
||||
|
||||
**Мониторит:**
|
||||
- Файлы загруженные через Nextcloud WebUI
|
||||
- Файлы созданные/изменённые в Nextcloud
|
||||
|
||||
**Метод:**
|
||||
- Nextcloud Activity API (polling каждые 30 сек)
|
||||
- Разбивает агрегированные события на отдельные файлы
|
||||
- БЕЗ дубликатов!
|
||||
|
||||
**Формат события:**
|
||||
```json
|
||||
{
|
||||
"type": "file_created",
|
||||
"source": "nextcloud_activity",
|
||||
"timestamp": "2025-10-30T12:53:40+00:00",
|
||||
"file_id": 73460,
|
||||
"path": "/experimental_report.xlsx",
|
||||
"filename": "experimental_report.xlsx",
|
||||
"user": "admin",
|
||||
"action": "created"
|
||||
}
|
||||
```
|
||||
|
||||
**Команды:**
|
||||
```bash
|
||||
# Статус
|
||||
ps aux | grep nextcloud_activity_monitor
|
||||
|
||||
# Логи
|
||||
tail -f nextcloud_activity.log
|
||||
|
||||
# Остановить
|
||||
pkill -f nextcloud_activity_monitor.js
|
||||
|
||||
# Запустить
|
||||
cd /var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/file_storage
|
||||
nohup /usr/bin/nodejs nextcloud_activity_monitor.js > nextcloud_activity.log 2>&1 &
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣ S3 Monitor (Docker)
|
||||
**Контейнер:** `s3-monitor`
|
||||
**Запущен:** ✅ (Up 40 минут)
|
||||
**Скрипт:** `s3_monitor_docker.js`
|
||||
|
||||
**Мониторит:**
|
||||
- Файлы загруженные напрямую в S3
|
||||
- Через S3 Browser, aws-cli, Cyberduck, скрипты
|
||||
- Любые изменения в bucket
|
||||
|
||||
**Метод:**
|
||||
- S3 ListObjectsV2 API (polling каждые 30 сек)
|
||||
- Pagination - получает ВСЕ файлы (24,522 файла!)
|
||||
- Сравнение по ETag
|
||||
|
||||
**Bucket:** `f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c`
|
||||
|
||||
**Формат события:**
|
||||
```json
|
||||
{
|
||||
"type": "file_created",
|
||||
"source": "s3_monitor",
|
||||
"timestamp": "2025-10-30T20:49:31.593Z",
|
||||
"path": "crm2/CRM_Active_Files/Documents/file.xlsx",
|
||||
"filename": "file.xlsx",
|
||||
"size": 8224,
|
||||
"etag": "7004954627252c9d0a7e6417f8325d07",
|
||||
"last_modified": "2025-10-30T20:49:14.132Z",
|
||||
"action": "created"
|
||||
}
|
||||
```
|
||||
|
||||
**Команды:**
|
||||
```bash
|
||||
# Статус
|
||||
docker ps | grep s3-monitor
|
||||
|
||||
# Логи
|
||||
docker logs s3-monitor -f
|
||||
|
||||
# Остановить
|
||||
docker stop s3-monitor
|
||||
|
||||
# Запустить
|
||||
docker start s3-monitor
|
||||
|
||||
# Перезапустить
|
||||
docker restart s3-monitor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3️⃣ Redis Канал (центральная шина)
|
||||
**Host:** `147.45.146.17`
|
||||
**Port:** `6379`
|
||||
**Password:** `CRM_Redis_Pass_2025_Secure!`
|
||||
**Channel:** `crm:file:events`
|
||||
|
||||
**Подписаться:**
|
||||
```bash
|
||||
redis-cli -h 147.45.146.17 -p 6379 -a 'CRM_Redis_Pass_2025_Secure!' \
|
||||
SUBSCRIBE crm:file:events
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚫 ЧТО ОСТАНОВЛЕНО:
|
||||
|
||||
### ❌ redis_bridge.js
|
||||
**Причина:** Давал неполные данные, много дубликатов
|
||||
**Заменён на:** Nextcloud Activity Monitor (даёт чистые данные)
|
||||
|
||||
---
|
||||
|
||||
## 📊 СРАВНЕНИЕ ДАННЫХ:
|
||||
|
||||
| Поле | Nextcloud Activity | S3 Monitor |
|
||||
|------|-------------------|------------|
|
||||
| **type** | ✅ file_created/changed/deleted | ✅ file_created/modified/deleted |
|
||||
| **source** | nextcloud_activity | s3_monitor |
|
||||
| **filename** | ✅ | ✅ |
|
||||
| **path** | ✅ (Nextcloud путь) | ✅ (S3 полный путь) |
|
||||
| **file_id** | ✅ | ❌ |
|
||||
| **user** | ✅ | ❌ |
|
||||
| **size** | ❌ | ✅ |
|
||||
| **etag** | ❌ | ✅ |
|
||||
| **mime_type** | ❌ | ❌ |
|
||||
|
||||
**Дополняют друг друга!** 🎯
|
||||
|
||||
---
|
||||
|
||||
## 🔧 АВТОЗАПУСК ПРИ ПЕРЕЗАГРУЗКЕ СЕРВЕРА:
|
||||
|
||||
### S3 Monitor:
|
||||
✅ Уже настроен (`--restart unless-stopped`)
|
||||
|
||||
### Nextcloud Activity Monitor:
|
||||
Создам systemd service:
|
||||
|
||||
```bash
|
||||
sudo tee /etc/systemd/system/nextcloud-activity-monitor.service << 'EOF'
|
||||
[Unit]
|
||||
Description=Nextcloud Activity Monitor
|
||||
After=network.target redis.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/file_storage
|
||||
ExecStart=/usr/bin/nodejs nextcloud_activity_monitor.js
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
StandardOutput=append:/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/file_storage/nextcloud_activity.log
|
||||
StandardError=append:/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/file_storage/nextcloud_activity.log
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable nextcloud-activity-monitor
|
||||
sudo systemctl start nextcloud-activity-monitor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 ГОТОВО!
|
||||
|
||||
**Система полностью работает в production режиме!** 🚀
|
||||
|
||||
Хочешь чтобы я создал systemd service для автозапуска Nextcloud Activity Monitor? 😊
|
||||
104
crm_extensions/file_storage/QUICK_START.md
Normal file
104
crm_extensions/file_storage/QUICK_START.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# ⚡ Quick Start - Мониторинг файлов
|
||||
|
||||
## 🎯 Быстрая проверка системы
|
||||
|
||||
```bash
|
||||
bash START.sh
|
||||
```
|
||||
|
||||
Покажет статус всех компонентов и запустит остановленные.
|
||||
|
||||
---
|
||||
|
||||
## 📊 Что работает прямо сейчас:
|
||||
|
||||
### ✅ Nextcloud Activity Monitor
|
||||
- **PID:** 2122
|
||||
- **Лог:** `nextcloud_activity.log`
|
||||
- **Что делает:** Ловит файлы из Nextcloud WebUI
|
||||
- **Задержка:** 30 сек
|
||||
|
||||
### ✅ S3 Monitor (Docker)
|
||||
- **Контейнер:** `s3-monitor`
|
||||
- **Uptime:** 40+ минут
|
||||
- **Что делает:** Ловит файлы загруженные напрямую в S3
|
||||
- **Задержка:** 30 сек
|
||||
- **Файлов:** 24,522
|
||||
|
||||
### ✅ Redis канал
|
||||
- **Адрес:** `147.45.146.17:6379`
|
||||
- **Канал:** `crm:file:events`
|
||||
- **Статус:** Оба монитора публикуют события ✅
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Быстрый тест
|
||||
|
||||
### Тест 1: Загрузи файл в Nextcloud
|
||||
```bash
|
||||
# В другом терминале подпишись на события
|
||||
redis-cli -h 147.45.146.17 -p 6379 -a 'CRM_Redis_Pass_2025_Secure!' \
|
||||
SUBSCRIBE crm:file:events
|
||||
```
|
||||
|
||||
Загрузи файл через Nextcloud WebUI → через 30 сек увидишь событие!
|
||||
|
||||
### Тест 2: Загрузи файл в S3
|
||||
```bash
|
||||
echo "test" > /tmp/test.txt
|
||||
aws s3 cp /tmp/test.txt \
|
||||
s3://f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c/test_$(date +%s).txt \
|
||||
--endpoint-url https://s3.twcstorage.ru
|
||||
```
|
||||
|
||||
Через 0-30 сек увидишь событие!
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Команды управления
|
||||
|
||||
```bash
|
||||
# Проверка статуса
|
||||
bash START.sh
|
||||
|
||||
# Логи в реальном времени
|
||||
tail -f nextcloud_activity.log
|
||||
docker logs s3-monitor -f
|
||||
|
||||
# Остановка
|
||||
pkill -f nextcloud_activity_monitor.js
|
||||
docker stop s3-monitor
|
||||
|
||||
# Запуск
|
||||
bash START.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Документация
|
||||
|
||||
- `README.md` - обзор системы
|
||||
- `PRODUCTION_READY.md` - production конфигурация
|
||||
- `ARCHITECTURE.md` - архитектура
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Troubleshooting
|
||||
|
||||
**Нет событий?**
|
||||
1. Проверь статус: `bash START.sh`
|
||||
2. Проверь логи: `tail -f nextcloud_activity.log`
|
||||
3. Проверь Redis: подпишись на канал
|
||||
|
||||
**Много дубликатов?**
|
||||
- От Nextcloud: дедупликация включена (1 событие вместо 6)
|
||||
- От S3: дубликатов нет
|
||||
|
||||
**Нужна помощь?**
|
||||
- Читай `PRODUCTION_READY.md`
|
||||
- Проверяй логи
|
||||
|
||||
---
|
||||
|
||||
**Всё готово! 🚀**
|
||||
|
||||
137
crm_extensions/file_storage/README.md
Normal file
137
crm_extensions/file_storage/README.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# 🔔 Система мониторинга файлов CRM
|
||||
|
||||
Автоматический мониторинг изменений файлов в S3 и Nextcloud с публикацией событий в Redis.
|
||||
|
||||
## 🎯 Компоненты системы
|
||||
|
||||
### 1. Nextcloud Activity Monitor
|
||||
**Файл:** `nextcloud_activity_monitor.js`
|
||||
**Назначение:** Мониторинг файлов загруженных через Nextcloud WebUI
|
||||
**Метод:** Nextcloud Activity API
|
||||
**Интервал:** 30 секунд
|
||||
|
||||
**Запуск:**
|
||||
```bash
|
||||
cd /var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/file_storage
|
||||
nohup /usr/bin/nodejs nextcloud_activity_monitor.js > nextcloud_activity.log 2>&1 &
|
||||
```
|
||||
|
||||
**Управление:**
|
||||
```bash
|
||||
ps aux | grep nextcloud_activity_monitor # Статус
|
||||
tail -f nextcloud_activity.log # Логи
|
||||
pkill -f nextcloud_activity_monitor.js # Остановить
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. S3 Monitor (Docker)
|
||||
**Контейнер:** `s3-monitor`
|
||||
**Назначение:** Мониторинг файлов загруженных напрямую в S3
|
||||
**Bucket:** `f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c`
|
||||
**Интервал:** 30 секунд
|
||||
**Файлов:** ~24,500
|
||||
|
||||
**Управление:**
|
||||
```bash
|
||||
docker ps | grep s3-monitor # Статус
|
||||
docker logs s3-monitor -f # Логи
|
||||
docker stop s3-monitor # Остановить
|
||||
docker start s3-monitor # Запустить
|
||||
docker restart s3-monitor # Перезапустить
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Redis (центральная шина событий)
|
||||
**Адрес:** `147.45.146.17:6379`
|
||||
**Канал:** `crm:file:events`
|
||||
**Password:** `CRM_Redis_Pass_2025_Secure!`
|
||||
|
||||
**Подписка:**
|
||||
```bash
|
||||
redis-cli -h 147.45.146.17 -p 6379 -a 'CRM_Redis_Pass_2025_Secure!' \
|
||||
SUBSCRIBE crm:file:events
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Формат событий
|
||||
|
||||
### От Nextcloud Activity Monitor:
|
||||
```json
|
||||
{
|
||||
"type": "file_created",
|
||||
"source": "nextcloud_activity",
|
||||
"timestamp": "2025-10-30T12:53:40+00:00",
|
||||
"file_id": 73460,
|
||||
"path": "/experimental_report.xlsx",
|
||||
"filename": "experimental_report.xlsx",
|
||||
"user": "admin",
|
||||
"action": "created"
|
||||
}
|
||||
```
|
||||
|
||||
### От S3 Monitor:
|
||||
```json
|
||||
{
|
||||
"type": "file_created",
|
||||
"source": "s3_monitor",
|
||||
"timestamp": "2025-10-30T20:49:31.593Z",
|
||||
"path": "crm2/CRM_Active_Files/Documents/file.xlsx",
|
||||
"filename": "file.xlsx",
|
||||
"size": 8224,
|
||||
"etag": "7004954627252c9d0a7e6417f8325d07",
|
||||
"last_modified": "2025-10-30T20:49:14.132Z",
|
||||
"action": "created"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Credentials
|
||||
|
||||
### Nextcloud API:
|
||||
```
|
||||
URL: https://office.clientright.ru:8443
|
||||
User: admin
|
||||
App Password: tGHKS-3cC9m-7Hggb-65Awk-zxWQE
|
||||
```
|
||||
|
||||
### S3 (TWC Storage):
|
||||
```
|
||||
Endpoint: https://s3.twcstorage.ru
|
||||
Region: ru-1
|
||||
Access Key: 2OMAK5ZNM900TAXM16J7
|
||||
Secret Key: f4ADllb5VZBAt2HdsyB8WcwVEU7U74MwFCa1DARG
|
||||
Bucket: f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Дополнительные документы
|
||||
|
||||
- `PRODUCTION_READY.md` - полное описание production конфигурации
|
||||
- `ARCHITECTURE.md` - схема архитектуры системы
|
||||
- `S3_MONITORING_GUIDE.md` - руководство по S3 мониторингу
|
||||
- `NEXTCLOUD_API_OVERVIEW.md` - обзор Nextcloud API
|
||||
- `SETUP_CHECKLIST.md` - чек-лист настройки
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Для разработчиков
|
||||
|
||||
### n8n Workflows (готовые для импорта):
|
||||
- `n8n_s3_event_processor.json` - обработчик событий из Redis
|
||||
- `n8n_nextcloud_activity_monitor.json` - альтернатива через n8n
|
||||
- `n8n_s3_monitor_workflow.json` - альтернатива S3 Monitor через n8n
|
||||
|
||||
### Утилиты:
|
||||
- `get_s3_credentials.sh` - получение S3 credentials
|
||||
|
||||
---
|
||||
|
||||
**Дата создания:** 30 октября 2025
|
||||
**Версия:** 1.0
|
||||
**Статус:** Production Ready ✅
|
||||
|
||||
248
crm_extensions/file_storage/S3_MONITORING_GUIDE.md
Normal file
248
crm_extensions/file_storage/S3_MONITORING_GUIDE.md
Normal file
@@ -0,0 +1,248 @@
|
||||
# 🔍 Руководство по мониторингу S3
|
||||
|
||||
## Проблема
|
||||
Тебе нужно ловить **любые** изменения файлов в S3 bucket, даже если они загружены:
|
||||
- Вручную через S3 Browser / Cyberduck
|
||||
- Из внешних скриптов
|
||||
- Не через CRM или Nextcloud
|
||||
|
||||
## ❌ Почему не Event Notifications?
|
||||
TWC Storage **не поддерживает** S3 Event Notifications официально (не документировано).
|
||||
|
||||
## ✅ РЕШЕНИЕ: n8n Workflow с Polling
|
||||
|
||||
### Как работает:
|
||||
```
|
||||
n8n каждые 30 сек → Список файлов в S3 → Сравнение с предыдущим состоянием →
|
||||
→ Обнаружены изменения → Публикация в Redis → Твои обработчики
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Пошаговая инструкция
|
||||
|
||||
### Шаг 1: Импортируй workflow в n8n
|
||||
|
||||
1. Открой n8n: https://n8n.clientright.pro
|
||||
2. Создай новый workflow
|
||||
3. Нажми **Import from File**
|
||||
4. Загрузи файл: `n8n_s3_monitor_workflow.json`
|
||||
|
||||
### Шаг 2: Настрой S3 Credentials
|
||||
|
||||
В n8n создай credentials:
|
||||
|
||||
**Тип:** AWS S3
|
||||
**Имя:** `TWC S3`
|
||||
|
||||
**Параметры:**
|
||||
```
|
||||
Access Key ID: [твой S3 ключ из Nextcloud]
|
||||
Secret Access Key: [твой S3 секрет из Nextcloud]
|
||||
Region: ru-1
|
||||
|
||||
Custom Endpoints: ✅ (включить!)
|
||||
S3 Endpoint: https://s3.twcstorage.ru
|
||||
Force Path Style: ✅ (включить!)
|
||||
```
|
||||
|
||||
**Как получить ключи:**
|
||||
```bash
|
||||
docker exec nextcloud-fresh php occ files_external:list --output=json | jq -r '.[0].configuration | "Key: \(.key)\nSecret: \(.secret)\nBucket: \(.bucket)"'
|
||||
```
|
||||
|
||||
### Шаг 3: Настрой Redis Credentials
|
||||
|
||||
**Тип:** Redis
|
||||
**Имя:** `CRM Redis`
|
||||
|
||||
**Параметры:**
|
||||
```
|
||||
Host: 147.45.146.17
|
||||
Port: 6379
|
||||
Password: CRM_Redis_Pass_2025_Secure!
|
||||
Database: 0
|
||||
```
|
||||
|
||||
### Шаг 4: Настрой Environment Variables (если нужно)
|
||||
|
||||
В n8n Settings → Environment Variables:
|
||||
```
|
||||
S3_BUCKET=f9825c87-18698658-c378-4aa7-91cc-0c131bebccda
|
||||
WEBHOOK_URL=https://твой-эндпоинт.com/webhook (опционально)
|
||||
```
|
||||
|
||||
### Шаг 5: Настрой Bucket и Prefix
|
||||
|
||||
В ноде **List S3 Files**:
|
||||
- `Bucket Name`: укажи свой bucket (или используй `{{$env.S3_BUCKET}}`)
|
||||
- `Prefix`: укажи папку для мониторинга (например `nextcloud/data/admin/files/`)
|
||||
- Оставь пустым для мониторинга всего bucket
|
||||
|
||||
### Шаг 6: Активируй Workflow
|
||||
|
||||
1. Нажми **Active** (включи workflow)
|
||||
2. Первый запуск создаст начальное состояние файлов
|
||||
3. Последующие запуски будут сравнивать с предыдущим состоянием
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Тестирование
|
||||
|
||||
### 1. Загрузи тестовый файл в S3
|
||||
|
||||
Через S3 Browser, Cyberduck или aws-cli:
|
||||
```bash
|
||||
echo "test" > test.txt
|
||||
aws s3 cp test.txt s3://твой-bucket/test.txt \
|
||||
--endpoint-url https://s3.twcstorage.ru
|
||||
```
|
||||
|
||||
### 2. Подожди 30 секунд (интервал polling)
|
||||
|
||||
### 3. Проверь Redis
|
||||
|
||||
```bash
|
||||
redis-cli -h 147.45.146.17 -p 6379 -a 'CRM_Redis_Pass_2025_Secure!' \
|
||||
SUBSCRIBE crm:file:events
|
||||
```
|
||||
|
||||
Должно прийти:
|
||||
```json
|
||||
{
|
||||
"type": "file_created",
|
||||
"source": "s3_monitor",
|
||||
"timestamp": "2025-10-30T12:34:56Z",
|
||||
"path": "test.txt",
|
||||
"filename": "test.txt",
|
||||
"size": 5,
|
||||
"etag": "\"098f6bcd4621d373cade4e832627b4f6\"",
|
||||
"last_modified": "2025-10-30T12:34:50Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Настройка интервала
|
||||
|
||||
Измени интервал проверки в ноде **"Каждые 30 секунд"**:
|
||||
|
||||
- **10 секунд** - почти реал-тайм, но больше нагрузка на S3 API
|
||||
- **30 секунд** - оптимальный баланс (рекомендуется)
|
||||
- **60 секунд** - экономия API запросов
|
||||
|
||||
**Важно:** TWC может лимитировать количество API запросов!
|
||||
|
||||
---
|
||||
|
||||
## 📊 Типы событий
|
||||
|
||||
Workflow генерирует 3 типа событий:
|
||||
|
||||
1. **`file_created`** - новый файл появился в bucket
|
||||
2. **`file_modified`** - файл изменился (другой ETag)
|
||||
3. **`file_deleted`** - файл удалён из bucket
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Обработка событий в других workflows
|
||||
|
||||
Создай новый workflow в n8n:
|
||||
|
||||
### Trigger: Redis (Subscribe)
|
||||
```
|
||||
Host: 147.45.146.17
|
||||
Port: 6379
|
||||
Password: CRM_Redis_Pass_2025_Secure!
|
||||
Channel: crm:file:events
|
||||
```
|
||||
|
||||
### Filter: По типу события
|
||||
```javascript
|
||||
// Обрабатываем только создание файлов
|
||||
return $json.type === 'file_created';
|
||||
```
|
||||
|
||||
### Switch: По расширению файла
|
||||
```javascript
|
||||
const ext = $json.filename.split('.').pop().toLowerCase();
|
||||
|
||||
switch(ext) {
|
||||
case 'pdf':
|
||||
return [0]; // Обработка PDF
|
||||
case 'jpg':
|
||||
case 'png':
|
||||
return [1]; // Обработка изображений
|
||||
case 'xlsx':
|
||||
case 'csv':
|
||||
return [2]; // Обработка таблиц
|
||||
default:
|
||||
return [3]; // Остальные файлы
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Пример: Автоматическая обработка ERV файлов
|
||||
|
||||
```
|
||||
Redis Subscribe (crm:file:events) →
|
||||
→ Filter (filename contains "erv") →
|
||||
→ Download from S3 →
|
||||
→ Process ERV →
|
||||
→ Upload to CRM →
|
||||
→ Notify user
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Мониторинг нескольких buckets
|
||||
|
||||
Если тебе нужно мониторить несколько buckets:
|
||||
|
||||
1. **Вариант 1:** Создай отдельный workflow для каждого bucket
|
||||
2. **Вариант 2:** Используй Loop в workflow для перебора buckets
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Важные замечания
|
||||
|
||||
1. **State сохраняется в Static Data** - не удаляй workflow без экспорта!
|
||||
2. **Первый запуск не генерирует события** - только создаёт начальное состояние
|
||||
3. **ETag используется для определения изменений** - если файл перезаписан с тем же содержимым, событие не сработает
|
||||
4. **Polling = задержка** - событие придёт через 0-30 секунд после реального изменения
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### Проблема: Не приходят события
|
||||
- Проверь что workflow **Active** (зелёная галочка)
|
||||
- Проверь логи workflow (Execute → View executions)
|
||||
- Проверь credentials (S3 и Redis)
|
||||
|
||||
### Проблема: Слишком много событий
|
||||
- Уменьши область мониторинга через `Prefix`
|
||||
- Увеличь интервал polling до 60-120 секунд
|
||||
|
||||
### Проблема: Пропускаются изменения
|
||||
- Уменьши интервал до 10-15 секунд
|
||||
- Проверь что State сохраняется (Static Data)
|
||||
|
||||
---
|
||||
|
||||
## 📚 Дополнительные материалы
|
||||
|
||||
- [n8n AWS S3 Node](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.awss3/)
|
||||
- [n8n Redis Node](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.redis/)
|
||||
- [n8n Code Node](https://docs.n8n.io/code-examples/methods-variables-reference/)
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Готово!
|
||||
|
||||
Теперь у тебя есть полноценный мониторинг S3 bucket в реальном времени!
|
||||
|
||||
Все изменения файлов будут автоматически публиковаться в Redis канал `crm:file:events`,
|
||||
откуда их могут подхватить другие твои системы (CRM, другие n8n workflows, и т.д.)
|
||||
|
||||
228
crm_extensions/file_storage/SETUP_CHECKLIST.md
Normal file
228
crm_extensions/file_storage/SETUP_CHECKLIST.md
Normal file
@@ -0,0 +1,228 @@
|
||||
# ✅ Чек-лист настройки S3 мониторинга
|
||||
|
||||
## 📋 Что нужно сделать
|
||||
|
||||
### Шаг 1: Получи S3 Credentials ✅
|
||||
|
||||
```bash
|
||||
cd /var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/file_storage
|
||||
bash get_s3_credentials.sh
|
||||
```
|
||||
|
||||
**Сохрани куда-нибудь:**
|
||||
- Access Key ID
|
||||
- Secret Access Key
|
||||
- Bucket name
|
||||
- Endpoint: `https://s3.twcstorage.ru`
|
||||
- Region: `ru-1`
|
||||
|
||||
---
|
||||
|
||||
### Шаг 2: Импортируй workflow в n8n ⏳
|
||||
|
||||
1. Открой https://n8n.clientright.pro
|
||||
2. Нажми **"+"** → **Import from File**
|
||||
3. Выбери файл: `/var/www/.../n8n_s3_monitor_workflow.json`
|
||||
4. Нажми **Import**
|
||||
|
||||
---
|
||||
|
||||
### Шаг 3: Настрой S3 Credentials в n8n ⏳
|
||||
|
||||
1. В n8n иди в **Settings** (⚙️) → **Credentials**
|
||||
2. Нажми **Add Credential** → выбери **AWS**
|
||||
3. Заполни:
|
||||
- **Name:** `TWC S3`
|
||||
- **Access Key ID:** (из шага 1)
|
||||
- **Secret Access Key:** (из шага 1)
|
||||
- **Region:** `ru-1`
|
||||
|
||||
4. **Включи** `Custom Endpoints` ✅
|
||||
5. **S3 Endpoint:** `https://s3.twcstorage.ru`
|
||||
6. **Включи** `Force Path Style` ✅
|
||||
7. Нажми **Test** → должно быть ✅
|
||||
8. **Save**
|
||||
|
||||
---
|
||||
|
||||
### Шаг 4: Настрой Redis Credentials в n8n ⏳
|
||||
|
||||
1. **Add Credential** → **Redis**
|
||||
2. Заполни:
|
||||
- **Name:** `CRM Redis`
|
||||
- **Host:** `147.45.146.17`
|
||||
- **Port:** `6379`
|
||||
- **Password:** `CRM_Redis_Pass_2025_Secure!`
|
||||
- **Database:** `0`
|
||||
3. **Test** → ✅
|
||||
4. **Save**
|
||||
|
||||
---
|
||||
|
||||
### Шаг 5: Настрой workflow ⏳
|
||||
|
||||
1. Открой импортированный workflow **"S3 File Monitor"**
|
||||
2. Кликни на ноду **"List S3 Files"**
|
||||
3. В поле **Credential for AWS** выбери `TWC S3`
|
||||
4. В поле **Bucket Name:**
|
||||
- Вариант 1: Впиши имя bucket из шага 1
|
||||
- Вариант 2: Оставь `{{$env.S3_BUCKET}}` и настрой env variable
|
||||
5. В поле **Prefix** (опционально):
|
||||
- Оставь пустым для мониторинга всего bucket
|
||||
- Или укажи папку, например: `nextcloud/data/admin/files/`
|
||||
|
||||
6. Кликни на ноду **"Publish to Redis"**
|
||||
7. Выбери credential: `CRM Redis`
|
||||
|
||||
8. **Save** workflow
|
||||
|
||||
---
|
||||
|
||||
### Шаг 6: Активируй workflow ⏳
|
||||
|
||||
1. Переключи тумблер **Active** в ON (вверху справа)
|
||||
2. Workflow начнёт работать!
|
||||
|
||||
**Первый запуск:**
|
||||
- Создаст начальное состояние файлов
|
||||
- **НЕ** сгенерирует события (это нормально!)
|
||||
|
||||
**Последующие запуски (каждые 30 сек):**
|
||||
- Будут сравнивать с предыдущим состоянием
|
||||
- Генерировать события при изменениях
|
||||
|
||||
---
|
||||
|
||||
### Шаг 7: Протестируй! 🧪
|
||||
|
||||
#### Тест 1: Загрузи файл в S3
|
||||
|
||||
Через **S3 Browser** или **aws-cli**:
|
||||
```bash
|
||||
echo "test content" > /tmp/test-file.txt
|
||||
|
||||
aws s3 cp /tmp/test-file.txt s3://ИМЯ-BUCKET/test-file.txt \
|
||||
--endpoint-url https://s3.twcstorage.ru
|
||||
```
|
||||
|
||||
#### Тест 2: Подожди 30 секунд
|
||||
|
||||
⏱️ Время для следующего polling цикла...
|
||||
|
||||
#### Тест 3: Проверь Redis
|
||||
|
||||
```bash
|
||||
redis-cli -h 147.45.146.17 -p 6379 -a 'CRM_Redis_Pass_2025_Secure!' \
|
||||
--csv SUBSCRIBE crm:file:events
|
||||
```
|
||||
|
||||
Должно прийти:
|
||||
```
|
||||
"subscribe","crm:file:events",1
|
||||
"message","crm:file:events","{\"type\":\"file_created\",\"source\":\"s3_monitor\",...}"
|
||||
```
|
||||
|
||||
#### Тест 4: Проверь n8n executions
|
||||
|
||||
1. В n8n → **S3 File Monitor** workflow
|
||||
2. Нажми **Executions** (внизу)
|
||||
3. Посмотри последние запуски
|
||||
4. Должны быть ✅ зелёные
|
||||
|
||||
---
|
||||
|
||||
### Шаг 8: Настрой обработчик событий (опционально) ⏳
|
||||
|
||||
Создай новый workflow в n8n для обработки событий:
|
||||
|
||||
1. **Trigger:** Redis (Subscribe)
|
||||
- Channel: `crm:file:events`
|
||||
- Credential: `CRM Redis`
|
||||
|
||||
2. **Filter** по типу файла:
|
||||
```javascript
|
||||
// Обрабатываем только PDF
|
||||
return $json.filename.endsWith('.pdf');
|
||||
```
|
||||
|
||||
3. **Твоя логика:**
|
||||
- Download from S3
|
||||
- Process
|
||||
- Upload to CRM
|
||||
- Notify user
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### ❌ Workflow не запускается
|
||||
- Проверь что он **Active** (зелёная галочка)
|
||||
- Проверь Executions → есть ли ошибки?
|
||||
|
||||
### ❌ S3 Connection failed
|
||||
- Проверь credentials (Access Key, Secret)
|
||||
- Проверь что включен `Force Path Style` ✅
|
||||
- Проверь endpoint: `https://s3.twcstorage.ru`
|
||||
|
||||
### ❌ Redis Connection failed
|
||||
- Проверь firewall: порт 6379 открыт?
|
||||
- Проверь пароль: `CRM_Redis_Pass_2025_Secure!`
|
||||
- Попробуй из терминала:
|
||||
```bash
|
||||
redis-cli -h 147.45.146.17 -p 6379 -a 'CRM_Redis_Pass_2025_Secure!' PING
|
||||
```
|
||||
|
||||
### ❌ События не приходят
|
||||
- Убедись что файл добавлен **в правильный bucket**
|
||||
- Убедись что файл **в нужной папке** (если указан Prefix)
|
||||
- Подожди 30 секунд для polling цикла
|
||||
- Проверь Static Data в workflow (сохраняется ли состояние?)
|
||||
|
||||
### ⚠️ Слишком много событий
|
||||
- Уменьши область мониторинга через `Prefix`
|
||||
- Увеличь интервал до 60-120 секунд
|
||||
|
||||
---
|
||||
|
||||
## 📊 Финальная проверка
|
||||
|
||||
После всех шагов у тебя должно быть:
|
||||
|
||||
- ✅ S3 credentials сохранены в n8n
|
||||
- ✅ Redis credentials сохранены в n8n
|
||||
- ✅ Workflow "S3 File Monitor" импортирован
|
||||
- ✅ Workflow активирован (Active = ON)
|
||||
- ✅ Тестовый файл загружен в S3
|
||||
- ✅ Событие пришло в Redis канал `crm:file:events`
|
||||
- ✅ Execution в n8n показывает ✅ success
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Готово!
|
||||
|
||||
Теперь **любые** изменения в твоём S3 bucket будут автоматически:
|
||||
1. Обнаруживаться (каждые 30 сек)
|
||||
2. Публиковаться в Redis (`crm:file:events`)
|
||||
3. Доступны для обработки в n8n, CRM, и других сервисах
|
||||
|
||||
---
|
||||
|
||||
## 📚 Дополнительные файлы
|
||||
|
||||
- `S3_MONITORING_GUIDE.md` - полное руководство
|
||||
- `ARCHITECTURE.md` - схема архитектуры
|
||||
- `n8n_s3_monitor_workflow.json` - workflow для импорта
|
||||
- `get_s3_credentials.sh` - скрипт для получения credentials
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Нужна помощь?
|
||||
|
||||
Если что-то не работает:
|
||||
1. Проверь логи n8n executions
|
||||
2. Проверь Redis подключение
|
||||
3. Проверь S3 credentials
|
||||
4. Перечитай `S3_MONITORING_GUIDE.md`
|
||||
|
||||
**Удачи!** 🚀
|
||||
|
||||
76
crm_extensions/file_storage/START.sh
Executable file
76
crm_extensions/file_storage/START.sh
Executable file
@@ -0,0 +1,76 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Скрипт для запуска/проверки всех компонентов мониторинга файлов
|
||||
#
|
||||
|
||||
echo "🚀 CRM File Monitoring System"
|
||||
echo "════════════════════════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
cd /var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/file_storage
|
||||
|
||||
# Проверка S3 Monitor
|
||||
echo "1️⃣ S3 Monitor (Docker):"
|
||||
if docker ps | grep -q s3-monitor; then
|
||||
echo " ✅ Работает"
|
||||
docker ps --format " Статус: {{.Status}}" | grep s3-monitor
|
||||
else
|
||||
echo " ❌ НЕ работает"
|
||||
echo " Запускаем..."
|
||||
docker start s3-monitor 2>/dev/null || echo " ⚠️ Контейнер не существует, запустите вручную"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Проверка Nextcloud Activity Monitor
|
||||
echo "2️⃣ Nextcloud Activity Monitor:"
|
||||
if ps aux | grep -v grep | grep -q nextcloud_activity_monitor.js; then
|
||||
PID=$(ps aux | grep -v grep | grep nextcloud_activity_monitor.js | awk '{print $2}')
|
||||
echo " ✅ Работает (PID: $PID)"
|
||||
else
|
||||
echo " ❌ НЕ работает"
|
||||
echo " Запускаем..."
|
||||
nohup /usr/bin/nodejs nextcloud_activity_monitor.js > nextcloud_activity.log 2>&1 &
|
||||
sleep 2
|
||||
echo " ✅ Запущен (PID: $!)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Проверка Redis
|
||||
echo "3️⃣ Redis подключение:"
|
||||
if redis-cli -h 147.45.146.17 -p 6379 -a 'CRM_Redis_Pass_2025_Secure!' PING 2>&1 | grep -q PONG; then
|
||||
echo " ✅ Redis доступен"
|
||||
else
|
||||
echo " ❌ Redis недоступен"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "════════════════════════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo "📋 СТАТУС:"
|
||||
echo ""
|
||||
echo "Nextcloud Activity Monitor:"
|
||||
tail -3 nextcloud_activity.log 2>/dev/null | tail -1
|
||||
|
||||
echo ""
|
||||
echo "S3 Monitor:"
|
||||
docker logs s3-monitor 2>&1 | tail -3 | tail -1
|
||||
|
||||
echo ""
|
||||
echo "════════════════════════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo "📚 Команды:"
|
||||
echo ""
|
||||
echo "Логи в реальном времени:"
|
||||
echo " tail -f nextcloud_activity.log"
|
||||
echo " docker logs s3-monitor -f"
|
||||
echo ""
|
||||
echo "Подписка на события:"
|
||||
echo " redis-cli -h 147.45.146.17 -p 6379 -a 'CRM_Redis_Pass_2025_Secure!' SUBSCRIBE crm:file:events"
|
||||
echo ""
|
||||
echo "Документация:"
|
||||
echo " cat README.md"
|
||||
echo " cat PRODUCTION_READY.md"
|
||||
echo ""
|
||||
|
||||
233
crm_extensions/file_storage/api/create_nextcloud_file.php
Normal file
233
crm_extensions/file_storage/api/create_nextcloud_file.php
Normal file
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
/**
|
||||
* Создание нового файла в Nextcloud
|
||||
* Создаёт пустой DOCX/XLSX/PPTX и открывает для редактирования
|
||||
*/
|
||||
|
||||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/shared/EnvLoader.php';
|
||||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/vendor/autoload.php';
|
||||
EnvLoader::load('/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/.env');
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
// Параметры
|
||||
$module = $_GET['module'] ?? '';
|
||||
$recordId = $_GET['recordId'] ?? '';
|
||||
$recordName = $_GET['recordName'] ?? '';
|
||||
$fileName = $_GET['fileName'] ?? '';
|
||||
$fileType = $_GET['fileType'] ?? 'docx';
|
||||
|
||||
if (empty($module) || empty($recordId) || empty($fileName)) {
|
||||
die("❌ Не указаны обязательные параметры");
|
||||
}
|
||||
|
||||
// Nextcloud credentials
|
||||
$nextcloudUrl = 'https://office.clientright.ru:8443';
|
||||
$username = 'admin';
|
||||
$password = 'office';
|
||||
|
||||
// Определяем папку модуля
|
||||
$moduleFolders = [
|
||||
'Project' => 'Project',
|
||||
'Contacts' => 'Contacts',
|
||||
'Accounts' => 'Accounts',
|
||||
'Invoice' => 'Invoice',
|
||||
'Quotes' => 'Quotes',
|
||||
'SalesOrder' => 'SalesOrder',
|
||||
'PurchaseOrder' => 'PurchaseOrder',
|
||||
'HelpDesk' => 'HelpDesk',
|
||||
'Leads' => 'Leads',
|
||||
'Potentials' => 'Potentials'
|
||||
];
|
||||
|
||||
$moduleFolder = $moduleFolders[$module] ?? 'Other';
|
||||
|
||||
// Формируем имя папки записи
|
||||
$recordName = preg_replace('/[\/\\\\:\*\?"<>\|]/', '_', $recordName); // Убираем недопустимые символы
|
||||
$folderName = $recordName . '_' . $recordId;
|
||||
|
||||
// Формируем путь к файлу в Nextcloud
|
||||
$ncPath = "/crm/crm2/CRM_Active_Files/Documents/{$moduleFolder}/{$folderName}/{$fileName}.{$fileType}";
|
||||
$webdavUrl = $nextcloudUrl . '/remote.php/dav/files/' . $username . $ncPath;
|
||||
|
||||
error_log("=== CREATE NEXTCLOUD FILE ===");
|
||||
error_log("Module: " . $module);
|
||||
error_log("Record ID: " . $recordId);
|
||||
error_log("File name: " . $fileName);
|
||||
error_log("File type: " . $fileType);
|
||||
error_log("Nextcloud path: " . $ncPath);
|
||||
error_log("WebDAV URL: " . $webdavUrl);
|
||||
|
||||
// СОЗДАЁМ ФАЙЛ ЧЕРЕЗ NEXTCLOUD OCS API (Direct Editing)
|
||||
// Используем встроенный API Nextcloud для создания нового файла
|
||||
|
||||
$templateMap = [
|
||||
'docx' => 'onlyoffice',
|
||||
'xlsx' => 'onlyoffice',
|
||||
'pptx' => 'onlyoffice'
|
||||
];
|
||||
|
||||
$editorId = $templateMap[$fileType] ?? 'onlyoffice';
|
||||
|
||||
// Используем OCS API v2 для создания нового файла
|
||||
$createUrl = $nextcloudUrl . '/ocs/v2.php/apps/files/api/v1/directEditing/create';
|
||||
|
||||
$postData = http_build_query([
|
||||
'path' => $ncPath,
|
||||
'editorId' => $editorId,
|
||||
'templateId' => '',
|
||||
'templateType' => $fileType
|
||||
]);
|
||||
|
||||
error_log("Creating file via OCS API: " . $createUrl);
|
||||
error_log("Post data: " . $postData);
|
||||
|
||||
// Вызываем API создания
|
||||
$ch = curl_init($createUrl);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'OCS-APIRequest: true',
|
||||
'Content-Type: application/x-www-form-urlencoded',
|
||||
'Accept: application/json'
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_USERPWD, "$username:$password");
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
error_log("OCS API response code: " . $httpCode);
|
||||
error_log("OCS API response: " . substr($response, 0, 500));
|
||||
|
||||
// Если API сработал - парсим ответ и получаем URL редактора
|
||||
if ($httpCode === 200) {
|
||||
$data = json_decode($response, true);
|
||||
if (isset($data['ocs']['data']['url'])) {
|
||||
$editorUrl = $data['ocs']['data']['url'];
|
||||
error_log("Got editor URL from API: " . $editorUrl);
|
||||
header('Location: ' . $nextcloudUrl . $editorUrl);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Если API не сработал - создаём файл НАПРЯМУЮ В S3 и открываем через OnlyOffice!
|
||||
error_log("OCS API failed, creating file directly in S3");
|
||||
|
||||
// Извлекаем S3 путь из Nextcloud пути
|
||||
// /crm/crm2/CRM_Active_Files/... → crm2/CRM_Active_Files/...
|
||||
$s3Path = ltrim($ncPath, '/');
|
||||
|
||||
// S3 credentials
|
||||
$s3Client = new Aws\S3\S3Client([
|
||||
'version' => 'latest',
|
||||
'region' => 'ru-1',
|
||||
'endpoint' => 'https://s3.twcstorage.ru',
|
||||
'use_path_style_endpoint' => true,
|
||||
'credentials' => [
|
||||
'key' => EnvLoader::getRequired('S3_ACCESS_KEY'),
|
||||
'secret' => EnvLoader::getRequired('S3_SECRET_KEY')
|
||||
],
|
||||
'suppress_php_deprecation_warning' => true
|
||||
]);
|
||||
|
||||
$bucket = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
|
||||
|
||||
// Создаём минимальный пустой файл
|
||||
$emptyContent = createEmptyFile($fileType);
|
||||
|
||||
error_log("Creating file in S3: " . $s3Path . " (" . strlen($emptyContent) . " bytes)");
|
||||
|
||||
// Загружаем файл в S3
|
||||
try {
|
||||
$result = $s3Client->putObject([
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $s3Path,
|
||||
'Body' => $emptyContent,
|
||||
'ContentType' => getContentType($fileType)
|
||||
]);
|
||||
|
||||
error_log("✅ File created in S3!");
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Failed to create file in S3: " . $e->getMessage());
|
||||
die("❌ Не удалось создать файл в S3: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// Формируем S3 URL
|
||||
$s3Url = 'https://s3.twcstorage.ru/' . $bucket . '/' . $s3Path;
|
||||
|
||||
error_log("S3 URL: " . $s3Url);
|
||||
|
||||
// Публикуем событие в Redis для индексации Nextcloud
|
||||
try {
|
||||
// Используем Predis (установлен через composer)
|
||||
$redis = new Predis\Client([
|
||||
'scheme' => 'tcp',
|
||||
'host' => '147.45.146.17',
|
||||
'port' => 6379,
|
||||
'password' => 'CRM_Redis_Pass_2025_Secure!'
|
||||
]);
|
||||
|
||||
$event = json_encode([
|
||||
'type' => 'file_created',
|
||||
'source' => 'crm_create_file',
|
||||
'path' => $s3Path,
|
||||
'timestamp' => time()
|
||||
]);
|
||||
|
||||
$redis->publish('crm:file:events', $event);
|
||||
error_log("✅ Published event to Redis");
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Redis publish failed: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// Открываем файл НАПРЯМУЮ через OnlyOffice (быстро!)
|
||||
$redirectUrl = '/crm_extensions/file_storage/api/open_file_v2.php?recordId=' . urlencode($recordId) . '&fileName=' . urlencode($s3Url);
|
||||
|
||||
error_log("Redirecting to OnlyOffice: " . $redirectUrl);
|
||||
|
||||
// Редирект
|
||||
header('Location: ' . $redirectUrl);
|
||||
exit;
|
||||
|
||||
/**
|
||||
* Создаёт минимальное пустое содержимое для Office файла
|
||||
*/
|
||||
function createEmptyFile($fileType) {
|
||||
// Используем готовые минимальные шаблоны
|
||||
$templatePath = __DIR__ . '/../templates/empty.' . $fileType;
|
||||
|
||||
if (file_exists($templatePath)) {
|
||||
$content = file_get_contents($templatePath);
|
||||
error_log("Using template: " . $templatePath . " (" . strlen($content) . " bytes)");
|
||||
return $content;
|
||||
}
|
||||
|
||||
error_log("Template not found: " . $templatePath);
|
||||
|
||||
// Fallback: пустая строка (не будет работать, но хотя бы не упадёт)
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Определяет Content-Type для файла
|
||||
*/
|
||||
function getContentType($fileType) {
|
||||
$types = [
|
||||
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'doc' => 'application/msword',
|
||||
'xls' => 'application/vnd.ms-excel',
|
||||
'ppt' => 'application/vnd.ms-powerpoint'
|
||||
];
|
||||
return $types[$fileType] ?? 'application/octet-stream';
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
23
crm_extensions/file_storage/api/download_s3_file.php
Normal file
23
crm_extensions/file_storage/api/download_s3_file.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
/**
|
||||
* ПРОСТОЙ Прокси для скачивания файла из S3
|
||||
* Просто отдаём публичный URL - S3 сам разберётся
|
||||
*/
|
||||
|
||||
$s3Path = isset($_GET['path']) ? $_GET['path'] : '';
|
||||
|
||||
if (empty($s3Path)) {
|
||||
http_response_code(400);
|
||||
die('Missing path parameter');
|
||||
}
|
||||
|
||||
// Формируем публичный S3 URL
|
||||
$bucket = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
|
||||
$s3Url = 'https://s3.twcstorage.ru/' . $bucket . '/' . $s3Path;
|
||||
|
||||
error_log("download_s3_file.php: Redirecting to: $s3Url");
|
||||
|
||||
// РЕДИРЕКТИМ на S3 напрямую
|
||||
header('Location: ' . $s3Url);
|
||||
exit;
|
||||
?>
|
||||
40
crm_extensions/file_storage/api/index_file.php
Normal file
40
crm_extensions/file_storage/api/index_file.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* Быстрая индексация одного файла в Nextcloud
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$path = $input['path'] ?? '';
|
||||
|
||||
if (empty($path)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Missing path']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Формируем Nextcloud путь
|
||||
$ncPath = '/admin/files/crm/' . $path;
|
||||
|
||||
// Запускаем сканирование
|
||||
$command = "docker exec -u www-data nextcloud-fresh php occ files:scan --path=" . escapeshellarg($ncPath) . " 2>&1";
|
||||
|
||||
exec($command, $output, $returnCode);
|
||||
|
||||
if ($returnCode === 0) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'File indexed successfully',
|
||||
'output' => implode("\n", $output)
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Indexing failed',
|
||||
'output' => implode("\n", $output)
|
||||
]);
|
||||
}
|
||||
?>
|
||||
|
||||
|
||||
|
||||
97
crm_extensions/file_storage/api/nextcloud_open.php
Normal file
97
crm_extensions/file_storage/api/nextcloud_open.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/**
|
||||
* Открытие файла через Nextcloud + OnlyOffice
|
||||
* Для сравнения с прямым OnlyOffice
|
||||
*/
|
||||
|
||||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/shared/EnvLoader.php';
|
||||
EnvLoader::load('/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/.env');
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
$fileName = isset($_GET['fileName']) ? $_GET['fileName'] : '';
|
||||
$recordId = isset($_GET['recordId']) ? $_GET['recordId'] : '';
|
||||
|
||||
if (empty($fileName)) {
|
||||
die("❌ fileName не указан");
|
||||
}
|
||||
|
||||
// Извлекаем S3 путь
|
||||
$s3Path = '';
|
||||
if (strpos($fileName, 'http') === 0) {
|
||||
$fileName = urldecode($fileName);
|
||||
$bucketId = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
|
||||
$pos = strpos($fileName, $bucketId . '/');
|
||||
if ($pos !== false) {
|
||||
$s3Path = substr($fileName, $pos + strlen($bucketId) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($s3Path)) {
|
||||
die("❌ Не удалось извлечь путь из URL");
|
||||
}
|
||||
|
||||
// Nextcloud credentials
|
||||
$nextcloudUrl = 'https://office.clientright.ru:8443';
|
||||
$username = 'admin';
|
||||
$password = 'office';
|
||||
|
||||
// Формируем WebDAV путь
|
||||
$ncPath = '/crm/' . $s3Path;
|
||||
$webdavUrl = $nextcloudUrl . '/remote.php/dav/files/' . $username . $ncPath;
|
||||
|
||||
error_log("=== NEXTCLOUD OPEN ===");
|
||||
error_log("S3 Path: " . $s3Path);
|
||||
error_log("Nextcloud WebDAV: " . $webdavUrl);
|
||||
|
||||
// Получаем fileId через PROPFIND
|
||||
$ch = curl_init($webdavUrl);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PROPFIND');
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Depth: 0',
|
||||
'Content-Type: application/xml'
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, '<?xml version="1.0"?>
|
||||
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
|
||||
<d:prop>
|
||||
<oc:fileid/>
|
||||
</d:prop>
|
||||
</d:propfind>');
|
||||
curl_setopt($ch, CURLOPT_USERPWD, "$username:$password");
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
error_log("PROPFIND HTTP код: " . $httpCode);
|
||||
|
||||
if ($httpCode !== 207) {
|
||||
die("❌ Файл не найден в Nextcloud (HTTP $httpCode). Возможно, он не проиндексирован.");
|
||||
}
|
||||
|
||||
// Извлекаем fileId из XML
|
||||
preg_match('/<oc:fileid>(\d+)<\/oc:fileid>/', $response, $matches);
|
||||
if (!isset($matches[1])) {
|
||||
die("❌ Не удалось получить fileId");
|
||||
}
|
||||
|
||||
$fileId = $matches[1];
|
||||
error_log("Получен fileId: " . $fileId);
|
||||
|
||||
// Извлекаем директорию из пути
|
||||
$dirPath = dirname($ncPath);
|
||||
|
||||
// Формируем URL для открытия в Nextcloud
|
||||
// Nextcloud автоматически откроет OnlyOffice для редактирования
|
||||
$redirectUrl = $nextcloudUrl . '/apps/files/files/' . $fileId . '?dir=' . urlencode($dirPath) . '&openfile=true';
|
||||
|
||||
error_log("Redirect to: " . $redirectUrl);
|
||||
|
||||
// Редирект в Nextcloud
|
||||
header('Location: ' . $redirectUrl);
|
||||
exit;
|
||||
?>
|
||||
|
||||
130
crm_extensions/file_storage/api/onlyoffice_callback.php
Normal file
130
crm_extensions/file_storage/api/onlyoffice_callback.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/**
|
||||
* OnlyOffice Callback для сохранения файлов в S3
|
||||
*/
|
||||
|
||||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/shared/EnvLoader.php';
|
||||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/vendor/autoload.php';
|
||||
EnvLoader::load('/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/.env');
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 0);
|
||||
ini_set('log_errors', 1);
|
||||
ini_set('error_log', '/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/file_storage/onlyoffice_callback.log');
|
||||
|
||||
// Логируем все запросы
|
||||
$input = file_get_contents('php://input');
|
||||
$data = json_decode($input, true);
|
||||
|
||||
error_log("=== ONLYOFFICE CALLBACK ===");
|
||||
error_log("Method: " . $_SERVER['REQUEST_METHOD']);
|
||||
error_log("Body: " . $input);
|
||||
|
||||
// OnlyOffice отправляет POST с JSON данными
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($data)) {
|
||||
$status = $data['status'] ?? 0;
|
||||
$key = $data['key'] ?? 'unknown';
|
||||
|
||||
error_log("Callback Status: $status, Key: $key");
|
||||
|
||||
// Status 2 = файл сохранён, нужно скачать и загрузить в S3
|
||||
if ($status == 2 && isset($data['url'])) {
|
||||
$downloadUrl = $data['url'];
|
||||
error_log("File saved! Download URL: " . $downloadUrl);
|
||||
|
||||
try {
|
||||
// Скачиваем изменённый файл от OnlyOffice
|
||||
$fileContent = file_get_contents($downloadUrl);
|
||||
|
||||
if ($fileContent === false) {
|
||||
error_log("Failed to download file from OnlyOffice");
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 1]);
|
||||
exit;
|
||||
}
|
||||
|
||||
error_log("Downloaded file: " . strlen($fileContent) . " bytes");
|
||||
|
||||
// Получаем оригинальный путь файла из query параметра
|
||||
$s3Path = $_GET['s3Path'] ?? null;
|
||||
|
||||
if (!$s3Path) {
|
||||
error_log("ERROR: s3Path not provided in callback URL!");
|
||||
// Fallback: сохраняем во временную папку
|
||||
$s3Path = 'onlyoffice_saved/' . $key . '_' . date('Y-m-d_H-i-s') . '.docx';
|
||||
error_log("Using fallback path: " . $s3Path);
|
||||
} else {
|
||||
error_log("Saving to original path: " . $s3Path);
|
||||
}
|
||||
|
||||
// Инициализируем S3 клиент
|
||||
$s3Client = new Aws\S3\S3Client([
|
||||
'version' => 'latest',
|
||||
'region' => 'ru-1',
|
||||
'endpoint' => 'https://s3.twcstorage.ru',
|
||||
'use_path_style_endpoint' => true,
|
||||
'credentials' => [
|
||||
'key' => EnvLoader::getRequired('S3_ACCESS_KEY'),
|
||||
'secret' => EnvLoader::getRequired('S3_SECRET_KEY')
|
||||
],
|
||||
'suppress_php_deprecation_warning' => true
|
||||
]);
|
||||
|
||||
$bucket = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
|
||||
|
||||
// Сохраняем в ОРИГИНАЛЬНОЕ место!
|
||||
$savedPath = $s3Path;
|
||||
|
||||
// Определяем Content-Type на основе расширения файла
|
||||
$contentType = getContentType($savedPath);
|
||||
error_log("Content-Type: " . $contentType);
|
||||
|
||||
$result = $s3Client->putObject([
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $savedPath,
|
||||
'Body' => $fileContent,
|
||||
'ContentType' => $contentType
|
||||
]);
|
||||
|
||||
error_log("File saved to S3: " . $savedPath);
|
||||
error_log("S3 Response: " . json_encode($result->toArray()));
|
||||
|
||||
http_response_code(200);
|
||||
echo json_encode(['error' => 0]);
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error saving file to S3: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 1, 'message' => $e->getMessage()]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Другие статусы (1 = открыт, 4 = закрыт и т.д.)
|
||||
http_response_code(200);
|
||||
echo json_encode(['error' => 0]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Для всех остальных запросов - 200 OK
|
||||
http_response_code(200);
|
||||
echo json_encode(['error' => 0]);
|
||||
|
||||
/**
|
||||
* Определяет Content-Type на основе расширения файла
|
||||
*/
|
||||
function getContentType($filename) {
|
||||
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||
$types = [
|
||||
'doc' => 'application/msword',
|
||||
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'xls' => 'application/vnd.ms-excel',
|
||||
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'ppt' => 'application/vnd.ms-powerpoint',
|
||||
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
||||
];
|
||||
return $types[$ext] ?? 'application/octet-stream';
|
||||
}
|
||||
?>
|
||||
|
||||
121
crm_extensions/file_storage/api/onlyoffice_callback_v2.php
Normal file
121
crm_extensions/file_storage/api/onlyoffice_callback_v2.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/**
|
||||
* OnlyOffice Callback v2 с сохранением в оригинальный файл S3
|
||||
*/
|
||||
|
||||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/shared/EnvLoader.php';
|
||||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/vendor/autoload.php';
|
||||
EnvLoader::load('/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/.env');
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 0);
|
||||
|
||||
$input = file_get_contents('php://input');
|
||||
$data = json_decode($input, true);
|
||||
|
||||
error_log("=== ONLYOFFICE CALLBACK V2 ===");
|
||||
error_log("Body: " . $input);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($data)) {
|
||||
$status = $data['status'] ?? 0;
|
||||
$key = $data['key'] ?? 'unknown';
|
||||
|
||||
error_log("Status: $status, Key: $key");
|
||||
|
||||
// Status 2 = файл сохранён, нужно скачать и загрузить в S3
|
||||
if ($status == 2 && isset($data['url'])) {
|
||||
$downloadUrl = $data['url'];
|
||||
error_log("File saved! Downloading from: " . $downloadUrl);
|
||||
|
||||
try {
|
||||
// Подключаемся к Redis чтобы узнать оригинальный путь
|
||||
$redis = new Predis\Client([
|
||||
'scheme' => 'tcp',
|
||||
'host' => EnvLoader::getRequired('REDIS_HOST'),
|
||||
'port' => (int)EnvLoader::getRequired('REDIS_PORT'),
|
||||
'password' => EnvLoader::getRequired('REDIS_PASSWORD')
|
||||
]);
|
||||
|
||||
// Получаем маппинг
|
||||
$mapping = $redis->get("crm:onlyoffice:key:$key");
|
||||
|
||||
if (!$mapping) {
|
||||
error_log("ERROR: No mapping found for key $key");
|
||||
// Сохраняем в резервную папку
|
||||
$s3Path = 'onlyoffice_saved/' . $key . '_' . date('Y-m-d_H-i-s') . '.docx';
|
||||
} else {
|
||||
$mappingData = json_decode($mapping, true);
|
||||
$s3Path = $mappingData['s3_path'];
|
||||
error_log("Found mapping: $key → $s3Path");
|
||||
}
|
||||
|
||||
// Скачиваем файл от OnlyOffice
|
||||
$fileContent = file_get_contents($downloadUrl);
|
||||
|
||||
if ($fileContent === false) {
|
||||
throw new Exception("Failed to download file from OnlyOffice");
|
||||
}
|
||||
|
||||
error_log("Downloaded: " . strlen($fileContent) . " bytes");
|
||||
|
||||
// Инициализируем S3 клиент
|
||||
$s3Client = new Aws\S3\S3Client([
|
||||
'version' => 'latest',
|
||||
'region' => 'ru-1',
|
||||
'endpoint' => 'https://s3.twcstorage.ru',
|
||||
'use_path_style_endpoint' => true,
|
||||
'credentials' => [
|
||||
'key' => EnvLoader::getRequired('S3_ACCESS_KEY'),
|
||||
'secret' => EnvLoader::getRequired('S3_SECRET_KEY')
|
||||
],
|
||||
'suppress_php_deprecation_warning' => true
|
||||
]);
|
||||
|
||||
$bucket = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
|
||||
|
||||
// Загружаем в S3 (ПЕРЕЗАПИСЫВАЕМ оригинальный файл!)
|
||||
$result = $s3Client->putObject([
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $s3Path,
|
||||
'Body' => $fileContent,
|
||||
'ContentType' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'Metadata' => [
|
||||
'saved_by' => 'onlyoffice',
|
||||
'saved_at' => date('Y-m-d H:i:s')
|
||||
]
|
||||
]);
|
||||
|
||||
error_log("✅ File saved to S3: $s3Path");
|
||||
|
||||
// Публикуем событие в Redis
|
||||
$redis->publish('crm:file:events', json_encode([
|
||||
'type' => 'file_modified',
|
||||
'source' => 'onlyoffice',
|
||||
'timestamp' => date('c'),
|
||||
'path' => $s3Path,
|
||||
'size' => strlen($fileContent),
|
||||
'action' => 'updated_via_onlyoffice'
|
||||
]));
|
||||
|
||||
http_response_code(200);
|
||||
echo json_encode(['error' => 0]);
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("ERROR: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 1, 'message' => $e->getMessage()]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Другие статусы
|
||||
http_response_code(200);
|
||||
echo json_encode(['error' => 0]);
|
||||
exit;
|
||||
}
|
||||
|
||||
http_response_code(200);
|
||||
echo json_encode(['error' => 0]);
|
||||
?>
|
||||
|
||||
@@ -1,110 +1,237 @@
|
||||
<?php
|
||||
/**
|
||||
* Простой редирект на файл в Nextcloud БЕЗ CSRF проверок
|
||||
* Использует FilePathManager для новой структуры файлов
|
||||
* ФИНАЛ: OnlyOffice + Pre-signed S3 URL
|
||||
* Теперь с CORS и правильными настройками!
|
||||
*/
|
||||
|
||||
// Включаем отображение ошибок
|
||||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/shared/EnvLoader.php';
|
||||
EnvLoader::load('/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/.env');
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
// Подключаем конфигурацию и FilePathManager
|
||||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/config.inc.php';
|
||||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/file_storage/FilePathManager.php';
|
||||
|
||||
// Получаем параметры
|
||||
$fileName = isset($_GET['fileName']) ? $_GET['fileName'] : '';
|
||||
$recordId = isset($_GET['recordId']) ? $_GET['recordId'] : '';
|
||||
|
||||
// Если fileName содержит полный URL S3, извлекаем путь к файлу
|
||||
$ncPath = '';
|
||||
if (empty($fileName)) {
|
||||
die("❌ fileName не указан");
|
||||
}
|
||||
|
||||
// Извлекаем S3 путь
|
||||
$s3Path = '';
|
||||
if (strpos($fileName, 'http') === 0) {
|
||||
// Декодируем URL
|
||||
$fileName = urldecode($fileName);
|
||||
|
||||
// Извлекаем путь после bucket ID
|
||||
// Формат: https://s3.twcstorage.ru/BUCKET_ID/crm2/CRM_Active_Files/...
|
||||
$bucketId = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
|
||||
$pos = strpos($fileName, $bucketId . '/');
|
||||
if ($pos !== false) {
|
||||
$s3Path = substr($fileName, $pos + strlen($bucketId) + 1);
|
||||
|
||||
// Nextcloud путь = /crm/ + s3_path
|
||||
$ncPath = '/crm/' . $s3Path;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($ncPath)) {
|
||||
die("❌ Ошибка: Не удалось извлечь путь из URL: $fileName");
|
||||
if (empty($s3Path)) {
|
||||
die("❌ Не удалось извлечь путь из URL");
|
||||
}
|
||||
|
||||
// Настройки Nextcloud
|
||||
$nextcloudUrl = 'https://office.clientright.ru:8443';
|
||||
$username = 'admin';
|
||||
$password = 'office';
|
||||
// Извлекаем расширение файла
|
||||
$ext = strtolower(pathinfo($s3Path, PATHINFO_EXTENSION));
|
||||
|
||||
// Вспомогательная функция: кодирование пути по сегментам (WebDAV)
|
||||
$encodePath = function(array $segments) {
|
||||
return implode('/', array_map('rawurlencode', $segments));
|
||||
};
|
||||
// ПРЯМОЙ S3 URL (bucket публичный, CORS настроен!)
|
||||
$bucket = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
|
||||
$s3Url = 'https://s3.twcstorage.ru/' . $bucket . '/' . $s3Path;
|
||||
|
||||
// Получаем fileId через WebDAV PROPFIND
|
||||
$fileId = null;
|
||||
$propfindUrl = $nextcloudUrl . '/remote.php/dav/files/' . $username . $ncPath;
|
||||
// Генерируем версию и ключ документа
|
||||
$version = time();
|
||||
// СЛУЧАЙНЫЙ ключ при каждом запросе, чтобы OnlyOffice не использовал кеш!
|
||||
$documentKey = md5($s3Path . '_' . $version);
|
||||
|
||||
error_log("Nextcloud Editor: PROPFIND -> {$propfindUrl}");
|
||||
// ПРЯМОЙ S3 URL (bucket публичный, поэтому pre-signed URL не нужен!)
|
||||
// Bucket поддерживает Range requests и CORS из коробки
|
||||
$fileUrl = $s3Url;
|
||||
|
||||
// XML запрос для получения fileid
|
||||
$xmlRequest = '<?xml version="1.0"?>
|
||||
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
|
||||
<d:prop>
|
||||
<oc:fileid/>
|
||||
</d:prop>
|
||||
</d:propfind>';
|
||||
// ОТЛАДКА: Логируем все параметры
|
||||
error_log("=== OPEN FILE DEBUG ===");
|
||||
error_log("S3 Path: " . $s3Path);
|
||||
error_log("File URL: " . $fileUrl);
|
||||
error_log("File extension: " . $ext);
|
||||
error_log("Document Key (unique): " . $documentKey);
|
||||
error_log("Version: " . $version);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $propfindUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_USERPWD, $username . ':' . $password);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PROPFIND');
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $xmlRequest);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Depth: 0',
|
||||
'Content-Type: application/xml'
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
$fileBasename = basename($s3Path);
|
||||
$fileType = getFileType($ext);
|
||||
$officeFormats = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
|
||||
|
||||
if ($response === false) {
|
||||
error_log("Nextcloud Editor: Ошибка cURL: " . $curlError);
|
||||
} else {
|
||||
error_log("Nextcloud Editor: HTTP код: {$httpCode}");
|
||||
|
||||
if ($httpCode === 207 && preg_match('/<oc:fileid>(\d+)<\/oc:fileid>/', $response, $matches)) {
|
||||
$fileId = (int)$matches[1];
|
||||
error_log("Nextcloud Editor: Получен fileId: {$fileId}");
|
||||
} else {
|
||||
error_log("Nextcloud Editor: Файл не найден по пути: {$ncPath} (HTTP {$httpCode})");
|
||||
}
|
||||
if (!in_array($ext, $officeFormats)) {
|
||||
header('Location: ' . $s3Url);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!$fileId) {
|
||||
$errorMsg = "❌ Ошибка: Не удалось получить fileId для файла {$fileName}";
|
||||
error_log("Nextcloud Editor ERROR: " . $errorMsg);
|
||||
die($errorMsg);
|
||||
}
|
||||
|
||||
// Формируем URL для Nextcloud
|
||||
// РАБОЧИЙ ФОРМАТ - редирект на файл с автооткрытием редактора!
|
||||
$redirectUrl = $nextcloudUrl . '/apps/files/files/' . $fileId . '?dir=/&editing=true&openfile=true';
|
||||
|
||||
// Логирование
|
||||
error_log("Nextcloud Editor: Redirect to $redirectUrl for file (ID: $fileId)");
|
||||
|
||||
// Делаем редирект
|
||||
header('Location: ' . $redirectUrl);
|
||||
exit;
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<title><?php echo htmlspecialchars($fileBasename); ?></title>
|
||||
<script src="https://office.clientright.ru:9443/web-apps/apps/api/documents/api.js?v=<?php echo time(); ?>"></script>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#editor {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="editor"></div>
|
||||
|
||||
<script>
|
||||
// Отладка в консоль
|
||||
console.log('📁 Файл:', <?php echo json_encode($fileBasename); ?>);
|
||||
console.log('🔗 S3 URL:', <?php echo json_encode($fileUrl); ?>);
|
||||
console.log('🔑 Document Key (unique):', <?php echo json_encode($documentKey); ?>);
|
||||
console.log('✅ Standalone OnlyOffice (9443) + Direct S3 URL!');
|
||||
|
||||
new DocsAPI.DocEditor("editor", {
|
||||
"documentType": "<?php echo $fileType; ?>",
|
||||
"document": {
|
||||
"fileType": "<?php echo $ext; ?>",
|
||||
"key": "<?php echo $documentKey; ?>",
|
||||
"title": <?php echo json_encode($fileBasename); ?>,
|
||||
"url": <?php echo json_encode($fileUrl); ?>,
|
||||
"permissions": {
|
||||
"comment": true,
|
||||
"download": true,
|
||||
"edit": true,
|
||||
"print": true,
|
||||
"review": true
|
||||
}
|
||||
},
|
||||
"editorConfig": {
|
||||
"mode": "edit",
|
||||
"lang": "ru",
|
||||
"callbackUrl": "https://crm.clientright.ru/crm_extensions/file_storage/api/onlyoffice_callback.php?s3Path=<?php echo urlencode($s3Path); ?>",
|
||||
"user": {
|
||||
"id": "user_<?php echo $recordId ?? 'guest'; ?>",
|
||||
"name": "CRM User"
|
||||
},
|
||||
"customization": {
|
||||
"autosave": true,
|
||||
"chat": false,
|
||||
"comments": true,
|
||||
"compactHeader": false,
|
||||
"compactToolbar": false,
|
||||
"help": true,
|
||||
"hideRightMenu": false,
|
||||
"logo": {
|
||||
"image": "https://crm.clientright.ru/layouts/v7/skins/images/logo.png",
|
||||
"imageEmbedded": "https://crm.clientright.ru/layouts/v7/skins/images/logo.png"
|
||||
},
|
||||
"zoom": 100
|
||||
}
|
||||
},
|
||||
"height": "100%",
|
||||
"width": "100%",
|
||||
"type": "desktop",
|
||||
"events": {
|
||||
"onReady": function() {
|
||||
console.log('✅ Editor ready!');
|
||||
},
|
||||
"onDocumentReady": function() {
|
||||
console.log('✅ Document loaded!');
|
||||
},
|
||||
"onError": function(event) {
|
||||
console.error('❌ OnlyOffice Error FULL:', JSON.stringify(event, null, 2));
|
||||
console.error('Event data:', event.data);
|
||||
console.error('Error code:', event.data.errorCode);
|
||||
console.error('Error description:', event.data.errorDescription);
|
||||
|
||||
// Тестируем доступность URL из браузера
|
||||
console.log('🧪 Testing S3 URL from browser...');
|
||||
fetch(<?php echo json_encode($fileUrl); ?>, { method: 'HEAD' })
|
||||
.then(response => {
|
||||
console.log('✅ Browser can access S3:', response.status);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('❌ Browser CANNOT access S3:', error);
|
||||
});
|
||||
|
||||
alert('Ошибка загрузки документа:\n\n' +
|
||||
'Code: ' + event.data.errorCode + '\n' +
|
||||
'Description: ' + event.data.errorDescription + '\n\n' +
|
||||
'Используется Pre-signed URL из S3\n\n' +
|
||||
'Смотри консоль браузера (F12) для деталей!');
|
||||
},
|
||||
"onWarning": function(event) {
|
||||
console.warn('⚠️ OnlyOffice Warning:', event);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
|
||||
function getFileType($ext) {
|
||||
if (in_array($ext, ['doc', 'docx'])) return 'word';
|
||||
if (in_array($ext, ['xls', 'xlsx'])) return 'cell';
|
||||
if (in_array($ext, ['ppt', 'pptx'])) return 'slide';
|
||||
return 'word';
|
||||
}
|
||||
|
||||
function generatePresignedUrl($s3Key, $expirationSeconds) {
|
||||
try {
|
||||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/vendor/autoload.php';
|
||||
|
||||
$s3Client = new Aws\S3\S3Client([
|
||||
'version' => 'latest',
|
||||
'region' => 'ru-1',
|
||||
'endpoint' => 'https://s3.twcstorage.ru',
|
||||
'use_path_style_endpoint' => true,
|
||||
'credentials' => [
|
||||
'key' => EnvLoader::getRequired('S3_ACCESS_KEY'),
|
||||
'secret' => EnvLoader::getRequired('S3_SECRET_KEY')
|
||||
],
|
||||
'suppress_php_deprecation_warning' => true
|
||||
]);
|
||||
|
||||
$bucket = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
|
||||
|
||||
// КЛЮЧ: Минимальные параметры = правильная подпись!
|
||||
$cmd = $s3Client->getCommand('GetObject', [
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $s3Key
|
||||
]);
|
||||
|
||||
$request = $s3Client->createPresignedRequest($cmd, "+{$expirationSeconds} seconds");
|
||||
return (string)$request->getUri();
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Pre-signed URL error: " . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getContentType($filename) {
|
||||
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||
$types = [
|
||||
'doc' => 'application/msword',
|
||||
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'xls' => 'application/vnd.ms-excel',
|
||||
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'ppt' => 'application/vnd.ms-powerpoint',
|
||||
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
||||
];
|
||||
return $types[$ext] ?? 'application/octet-stream';
|
||||
}
|
||||
?>
|
||||
|
||||
58
crm_extensions/file_storage/api/open_file_v3_collabora.php
Normal file
58
crm_extensions/file_storage/api/open_file_v3_collabora.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* Открытие файлов через Collabora (вместо OnlyOffice)
|
||||
* Collabora работает через Nextcloud WebDAV
|
||||
*/
|
||||
|
||||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/shared/EnvLoader.php';
|
||||
EnvLoader::load('/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/.env');
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
$fileName = isset($_GET['fileName']) ? $_GET['fileName'] : '';
|
||||
$recordId = isset($_GET['recordId']) ? $_GET['recordId'] : '';
|
||||
|
||||
if (empty($fileName)) {
|
||||
die("❌ fileName не указан");
|
||||
}
|
||||
|
||||
// Извлекаем S3 путь
|
||||
$s3Path = '';
|
||||
if (strpos($fileName, 'http') === 0) {
|
||||
$fileName = urldecode($fileName);
|
||||
$bucketId = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
|
||||
$pos = strpos($fileName, $bucketId . '/');
|
||||
if ($pos !== false) {
|
||||
$s3Path = substr($fileName, $pos + strlen($bucketId) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($s3Path)) {
|
||||
die("❌ Не удалось извлечь путь из URL");
|
||||
}
|
||||
|
||||
// Nextcloud path (убираем 'crm2/')
|
||||
$ncPath = str_replace('crm2/', '', $s3Path);
|
||||
$fileBasename = basename($ncPath);
|
||||
|
||||
// Nextcloud URL
|
||||
$nextcloudUrl = 'https://office.clientright.ru:8443';
|
||||
$webdavPath = '/crm/crm2/' . $ncPath;
|
||||
|
||||
// Открываем напрямую в Nextcloud Files - Collabora откроется автоматически!
|
||||
$dirPath = dirname($webdavPath);
|
||||
$redirectUrl = $nextcloudUrl . '/apps/files/?dir=' . urlencode($dirPath) . '&openfile=' . urlencode($fileBasename);
|
||||
|
||||
error_log("=== COLLABORA OPEN ===");
|
||||
error_log("S3 Path: " . $s3Path);
|
||||
error_log("NC WebDAV Path: " . $webdavPath);
|
||||
error_log("Redirect URL: " . $redirectUrl);
|
||||
|
||||
// Редиректим в Nextcloud
|
||||
header('Location: ' . $redirectUrl);
|
||||
exit;
|
||||
?>
|
||||
|
||||
|
||||
|
||||
169
crm_extensions/file_storage/api/open_file_v3_standalone.php
Normal file
169
crm_extensions/file_storage/api/open_file_v3_standalone.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
/**
|
||||
* OnlyOffice Standalone + S3 Direct URL
|
||||
* С сохранением обратно в S3
|
||||
*/
|
||||
|
||||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/shared/EnvLoader.php';
|
||||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/vendor/autoload.php';
|
||||
EnvLoader::load('/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/.env');
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
$fileName = isset($_GET['fileName']) ? $_GET['fileName'] : '';
|
||||
$recordId = isset($_GET['recordId']) ? $_GET['recordId'] : 'unknown';
|
||||
|
||||
if (empty($fileName)) {
|
||||
die("❌ fileName не указан");
|
||||
}
|
||||
|
||||
// Извлекаем S3 путь
|
||||
$s3Path = '';
|
||||
if (strpos($fileName, 'http') === 0) {
|
||||
$fileName = urldecode($fileName);
|
||||
$bucketId = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
|
||||
$pos = strpos($fileName, $bucketId . '/');
|
||||
if ($pos !== false) {
|
||||
$s3Path = substr($fileName, $pos + strlen($bucketId) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($s3Path)) {
|
||||
die("❌ Не удалось извлечь путь из URL");
|
||||
}
|
||||
|
||||
// Извлекаем расширение файла
|
||||
$ext = strtolower(pathinfo($s3Path, PATHINFO_EXTENSION));
|
||||
|
||||
// ПРЯМОЙ S3 URL (bucket публичный)
|
||||
$bucket = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
|
||||
$s3Url = 'https://s3.twcstorage.ru/' . $bucket . '/' . $s3Path;
|
||||
|
||||
// Генерируем уникальный ключ документа
|
||||
$version = time();
|
||||
$documentKey = md5($s3Path . '_' . $version);
|
||||
|
||||
// ВАЖНО: Сохраняем маппинг documentKey → S3 путь в Redis
|
||||
try {
|
||||
$redis = new Predis\Client([
|
||||
'scheme' => 'tcp',
|
||||
'host' => EnvLoader::getRequired('REDIS_HOST'),
|
||||
'port' => (int)EnvLoader::getRequired('REDIS_PORT'),
|
||||
'password' => EnvLoader::getRequired('REDIS_PASSWORD')
|
||||
]);
|
||||
|
||||
// Сохраняем на 24 часа (TTL = срок действия документа)
|
||||
$redis->setex("crm:onlyoffice:key:$documentKey", 86400, json_encode([
|
||||
's3_path' => $s3Path,
|
||||
'record_id' => $recordId,
|
||||
'created_at' => time()
|
||||
]));
|
||||
|
||||
error_log("Redis: Saved mapping for key $documentKey → $s3Path");
|
||||
} catch (Exception $e) {
|
||||
error_log("Redis error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$fileBasename = basename($s3Path);
|
||||
$fileType = getFileType($ext);
|
||||
$officeFormats = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
|
||||
|
||||
if (!in_array($ext, $officeFormats)) {
|
||||
header('Location: ' . $s3Url);
|
||||
exit;
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<title><?php echo htmlspecialchars($fileBasename); ?></title>
|
||||
<script src="https://office.clientright.ru:9443/web-apps/apps/api/documents/api.js?v=<?php echo time(); ?>"></script>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
#editor {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="editor"></div>
|
||||
|
||||
<script>
|
||||
console.log('📁 Файл:', <?php echo json_encode($fileBasename); ?>);
|
||||
console.log('🔗 S3 URL:', <?php echo json_encode($s3Url); ?>);
|
||||
console.log('🔑 Document Key:', <?php echo json_encode($documentKey); ?>);
|
||||
console.log('✅ Standalone OnlyOffice + Redis mapping!');
|
||||
|
||||
new DocsAPI.DocEditor("editor", {
|
||||
"documentType": "<?php echo $fileType; ?>",
|
||||
"document": {
|
||||
"fileType": "<?php echo $ext; ?>",
|
||||
"key": "<?php echo $documentKey; ?>",
|
||||
"title": <?php echo json_encode($fileBasename); ?>,
|
||||
"url": <?php echo json_encode($s3Url); ?>,
|
||||
"permissions": {
|
||||
"comment": true,
|
||||
"download": true,
|
||||
"edit": true,
|
||||
"print": true,
|
||||
"review": true
|
||||
}
|
||||
},
|
||||
"editorConfig": {
|
||||
"mode": "edit",
|
||||
"lang": "ru",
|
||||
"callbackUrl": "https://crm.clientright.ru/crm_extensions/file_storage/api/onlyoffice_callback_v2.php",
|
||||
"user": {
|
||||
"id": "user_<?php echo $recordId; ?>",
|
||||
"name": "CRM User"
|
||||
},
|
||||
"customization": {
|
||||
"autosave": true,
|
||||
"compactHeader": false,
|
||||
"compactToolbar": false
|
||||
}
|
||||
},
|
||||
"height": "100%",
|
||||
"width": "100%",
|
||||
"type": "desktop",
|
||||
"events": {
|
||||
"onReady": function() {
|
||||
console.log('✅ Editor ready!');
|
||||
},
|
||||
"onDocumentReady": function() {
|
||||
console.log('✅ Document loaded!');
|
||||
},
|
||||
"onError": function(event) {
|
||||
console.error('❌ Error:', event.data);
|
||||
alert('Ошибка: ' + event.data.errorDescription);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
|
||||
function getFileType($ext) {
|
||||
if (in_array($ext, ['doc', 'docx'])) return 'word';
|
||||
if (in_array($ext, ['xls', 'xlsx'])) return 'cell';
|
||||
if (in_array($ext, ['ppt', 'pptx'])) return 'slide';
|
||||
return 'word';
|
||||
}
|
||||
?>
|
||||
|
||||
121
crm_extensions/file_storage/api/s3_proxy.php
Normal file
121
crm_extensions/file_storage/api/s3_proxy.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/**
|
||||
* S3 Proxy для OnlyOffice
|
||||
* Проксирует запросы к S3, чтобы OnlyOffice мог загружать файлы
|
||||
*/
|
||||
|
||||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/shared/EnvLoader.php';
|
||||
EnvLoader::load('/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/.env');
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 0);
|
||||
|
||||
$path = isset($_GET['path']) ? $_GET['path'] : '';
|
||||
|
||||
if (empty($path)) {
|
||||
http_response_code(400);
|
||||
die('Path parameter is required');
|
||||
}
|
||||
|
||||
// CORS preflight
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, HEAD, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: *');
|
||||
header('Access-Control-Max-Age: 3600');
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Для HEAD запросов - только headers, без body
|
||||
$isHeadRequest = ($_SERVER['REQUEST_METHOD'] === 'HEAD');
|
||||
|
||||
// Формируем URL к S3
|
||||
$bucket = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
|
||||
$s3Url = 'https://s3.twcstorage.ru/' . $bucket . '/' . $path;
|
||||
|
||||
// Проверяем Range header (для OnlyOffice partial requests)
|
||||
$rangeHeader = isset($_SERVER['HTTP_RANGE']) ? $_SERVER['HTTP_RANGE'] : '';
|
||||
|
||||
error_log("S3 Proxy: Request from: " . ($_SERVER['REMOTE_ADDR'] ?? 'unknown'));
|
||||
error_log("S3 Proxy: Downloading: " . $s3Url);
|
||||
if ($rangeHeader) {
|
||||
error_log("S3 Proxy: Range request: " . $rangeHeader);
|
||||
}
|
||||
|
||||
// СНАЧАЛА скачиваем в буфер
|
||||
$ch = curl_init($s3Url);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // ← ВАЖНО: В БУФЕР!
|
||||
curl_setopt($ch, CURLOPT_HEADER, true); // ← Получаем headers
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 120);
|
||||
|
||||
// Для HEAD запросов - только headers
|
||||
if ($isHeadRequest) {
|
||||
curl_setopt($ch, CURLOPT_NOBODY, true);
|
||||
}
|
||||
|
||||
// Если есть Range header - передаём его в S3!
|
||||
if ($rangeHeader) {
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Range: ' . $rangeHeader]);
|
||||
}
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
// Проверяем ПРЕЖДЕ чем отправлять что-либо
|
||||
// 200 = полный файл, 206 = частичный (Range request)
|
||||
if ($response === false || ($httpCode !== 200 && $httpCode !== 206)) {
|
||||
error_log("S3 Proxy ERROR: HTTP $httpCode, cURL error: $error");
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
http_response_code($httpCode ?: 500);
|
||||
die('Failed to fetch file from S3');
|
||||
}
|
||||
|
||||
// Разделяем headers и body
|
||||
$headersText = substr($response, 0, $headerSize);
|
||||
$body = $isHeadRequest ? '' : substr($response, $headerSize); // Для HEAD body пустой
|
||||
|
||||
// Парсим headers
|
||||
$headers = explode("\r\n", $headersText);
|
||||
foreach ($headers as $header) {
|
||||
if (strpos($header, ':') !== false) {
|
||||
list($name, $value) = explode(':', $header, 2);
|
||||
$name = strtolower(trim($name));
|
||||
$value = trim($value);
|
||||
|
||||
// Пробрасываем нужные headers
|
||||
if (in_array($name, ['content-type', 'content-length', 'content-range', 'accept-ranges', 'etag', 'last-modified'])) {
|
||||
header($name . ': ' . $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CORS headers
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, HEAD, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: *');
|
||||
header('Access-Control-Expose-Headers: Content-Range, Accept-Ranges');
|
||||
|
||||
// Устанавливаем правильный HTTP код (206 для partial content)
|
||||
if ($httpCode === 206) {
|
||||
http_response_code(206);
|
||||
} else {
|
||||
http_response_code(200);
|
||||
}
|
||||
|
||||
// Отправляем body только для GET запросов (не для HEAD)
|
||||
if (!$isHeadRequest) {
|
||||
echo $body;
|
||||
error_log("S3 Proxy: Success! Sent " . strlen($body) . " bytes");
|
||||
} else {
|
||||
error_log("S3 Proxy: HEAD request completed");
|
||||
}
|
||||
?>
|
||||
|
||||
|
||||
|
||||
39
crm_extensions/file_storage/create_templates.php
Normal file
39
crm_extensions/file_storage/create_templates.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* Создание минимальных пустых шаблонов Office файлов
|
||||
*/
|
||||
|
||||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/vendor/autoload.php';
|
||||
|
||||
use PhpOffice\PhpWord\PhpWord;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpPresentation\PhpPresentation;
|
||||
|
||||
$templatesDir = __DIR__ . '/templates/';
|
||||
|
||||
// Создаём Word документ
|
||||
$phpWord = new PhpWord();
|
||||
$section = $phpWord->addSection();
|
||||
$section->addText('');
|
||||
$objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007');
|
||||
$objWriter->save($templatesDir . 'empty.docx');
|
||||
echo "✅ Created empty.docx\n";
|
||||
|
||||
// Создаём Excel таблицу
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
$sheet->setCellValue('A1', '');
|
||||
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
|
||||
$writer->save($templatesDir . 'empty.xlsx');
|
||||
echo "✅ Created empty.xlsx\n";
|
||||
|
||||
// Создаём PowerPoint презентацию
|
||||
$presentation = new PhpPresentation();
|
||||
$slide = $presentation->getActiveSlide();
|
||||
$writer = \PhpOffice\PhpPresentation\IOFactory::createWriter($presentation, 'PowerPoint2007');
|
||||
$writer->save($templatesDir . 'empty.pptx');
|
||||
echo "✅ Created empty.pptx\n";
|
||||
|
||||
echo "\n✅ Все шаблоны созданы!\n";
|
||||
?>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
onlyoffice-standalone:
|
||||
image: onlyoffice/documentserver:latest
|
||||
container_name: onlyoffice-standalone
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:8083:80" # Новый порт (8081 и 8082 заняты)
|
||||
environment:
|
||||
- JWT_ENABLED=false
|
||||
- JWT_SECRET=
|
||||
- WOPI_ENABLED=false
|
||||
volumes:
|
||||
- onlyoffice-standalone-data:/var/www/onlyoffice/Data
|
||||
- onlyoffice-standalone-logs:/var/log/onlyoffice
|
||||
- onlyoffice-standalone-fonts:/usr/share/fonts/truetype/custom
|
||||
- onlyoffice-standalone-forgotten:/var/lib/onlyoffice/documentserver/App_Data/cache/files/forgotten
|
||||
networks:
|
||||
- crm-network
|
||||
|
||||
volumes:
|
||||
onlyoffice-standalone-data:
|
||||
onlyoffice-standalone-logs:
|
||||
onlyoffice-standalone-fonts:
|
||||
onlyoffice-standalone-forgotten:
|
||||
|
||||
networks:
|
||||
crm-network:
|
||||
name: crm-network
|
||||
|
||||
48
crm_extensions/file_storage/get_s3_credentials.sh
Executable file
48
crm_extensions/file_storage/get_s3_credentials.sh
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "🔑 S3 Credentials для n8n"
|
||||
echo "════════════════════════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
# Получаем credentials из Nextcloud
|
||||
docker exec nextcloud-fresh php occ files_external:list --output=json 2>/dev/null | jq -r '
|
||||
.[0].configuration |
|
||||
"📦 Bucket: \(.bucket)
|
||||
🔑 Access Key: \(.key)
|
||||
🔐 Secret Key: \(.secret)
|
||||
🌐 Endpoint: \(.hostname)
|
||||
🗺️ Region: \(.region)
|
||||
|
||||
════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
📋 Для n8n AWS S3 Credentials:
|
||||
────────────────────────────────────────────────────────────────────────────────
|
||||
Access Key ID: \(.key)
|
||||
Secret Access Key: \(.secret)
|
||||
Region: \(.region)
|
||||
|
||||
Custom Endpoints: ✅ ВКЛЮЧИТЬ
|
||||
S3 Endpoint: https://\(.hostname)
|
||||
Force Path Style: ✅ ВКЛЮЧИТЬ
|
||||
────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
📋 Для n8n Environment Variables:
|
||||
────────────────────────────────────────────────────────────────────────────────
|
||||
S3_BUCKET=\(.bucket)
|
||||
S3_ENDPOINT=https://\(.hostname)
|
||||
S3_REGION=\(.region)
|
||||
────────────────────────────────────────────────────────────────────────────────
|
||||
"
|
||||
'
|
||||
|
||||
echo ""
|
||||
echo "📋 Redis Credentials для n8n:"
|
||||
echo "────────────────────────────────────────────────────────────────────────────────"
|
||||
echo "Host: 147.45.146.17"
|
||||
echo "Port: 6379"
|
||||
echo "Password: CRM_Redis_Pass_2025_Secure!"
|
||||
echo "Database: 0"
|
||||
echo "────────────────────────────────────────────────────────────────────────────────"
|
||||
echo ""
|
||||
echo "✅ Готово! Скопируй эти данные в n8n"
|
||||
|
||||
166
crm_extensions/file_storage/migrate_single_project.php
Normal file
166
crm_extensions/file_storage/migrate_single_project.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
/**
|
||||
* Простая миграция одного проекта в новую структуру
|
||||
* Использование: php migrate_single_project.php PROJECT_ID
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../../config.inc.php');
|
||||
require_once(__DIR__ . '/../../include/database/PearDatabase.php');
|
||||
|
||||
$adb = PearDatabase::getInstance();
|
||||
|
||||
// Получаем ID проекта
|
||||
$projectId = isset($argv[1]) ? (int)$argv[1] : null;
|
||||
|
||||
if (!$projectId) {
|
||||
echo "❌ Укажите ID проекта!\n";
|
||||
echo "Использование: php migrate_single_project.php PROJECT_ID\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "🔄 МИГРАЦИЯ ПРОЕКТА $projectId\n";
|
||||
echo "==========================================\n\n";
|
||||
|
||||
// Получаем информацию о проекте
|
||||
$result = $adb->pquery("SELECT p.projectname FROM vtiger_project p WHERE p.projectid = ?", [$projectId]);
|
||||
|
||||
if ($adb->num_rows($result) == 0) {
|
||||
echo "❌ Проект не найден!\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$projectName = $adb->query_result($result, 0, 'projectname');
|
||||
echo "📁 Проект: $projectName\n\n";
|
||||
|
||||
// Функция очистки имени файла
|
||||
function sanitizeName($name) {
|
||||
// Транслитерация
|
||||
$translitMap = [
|
||||
'А' => 'A', 'Б' => 'B', 'В' => 'V', 'Г' => 'G', 'Д' => 'D',
|
||||
'Е' => 'E', 'Ё' => 'E', 'Ж' => 'Zh', 'З' => 'Z', 'И' => 'I',
|
||||
'Й' => 'Y', 'К' => 'K', 'Л' => 'L', 'М' => 'M', 'Н' => 'N',
|
||||
'О' => 'O', 'П' => 'P', 'Р' => 'R', 'С' => 'S', 'Т' => 'T',
|
||||
'У' => 'U', 'Ф' => 'F', 'Х' => 'H', 'Ц' => 'Ts', 'Ч' => 'Ch',
|
||||
'Ш' => 'Sh', 'Щ' => 'Sch', 'Ъ' => '', 'Ы' => 'Y', 'Ь' => '',
|
||||
'Э' => 'E', 'Ю' => 'Yu', 'Я' => 'Ya',
|
||||
'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd',
|
||||
'е' => 'e', 'ё' => 'e', 'ж' => 'zh', 'з' => 'z', 'и' => 'i',
|
||||
'й' => 'y', 'к' => 'k', 'л' => 'l', 'м' => 'm', 'н' => 'n',
|
||||
'о' => 'o', 'п' => 'p', 'р' => 'r', 'с' => 's', 'т' => 't',
|
||||
'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'ts', 'ч' => 'ch',
|
||||
'ш' => 'sh', 'щ' => 'sch', 'ъ' => '', 'ы' => 'y', 'ь' => '',
|
||||
'э' => 'e', 'ю' => 'yu', 'я' => 'ya'
|
||||
];
|
||||
|
||||
$name = strtr($name, $translitMap);
|
||||
$name = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $name);
|
||||
$name = preg_replace('/_+/', '_', $name);
|
||||
$name = trim($name, '_');
|
||||
|
||||
if (strlen($name) > 100) {
|
||||
$name = substr($name, 0, 100);
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
$sanitizedName = sanitizeName($projectName);
|
||||
$newFolder = "Documents/Project/{$sanitizedName}_{$projectId}";
|
||||
|
||||
echo "📁 Новая папка: $newFolder\n\n";
|
||||
|
||||
// Получаем все документы проекта
|
||||
$result = $adb->pquery(
|
||||
"SELECT n.notesid, n.title, n.filename, n.s3_key, n.filelocationtype
|
||||
FROM vtiger_notes n
|
||||
JOIN vtiger_senotesrel snr ON snr.notesid = n.notesid
|
||||
JOIN vtiger_crmentity e ON e.crmid = n.notesid
|
||||
WHERE snr.crmid = ? AND e.deleted = 0 AND n.filelocationtype = 'E'
|
||||
ORDER BY n.notesid",
|
||||
[$projectId]
|
||||
);
|
||||
|
||||
$total = $adb->num_rows($result);
|
||||
echo "📊 Найдено документов: $total\n\n";
|
||||
|
||||
if ($total == 0) {
|
||||
echo "✅ Нет документов для миграции\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$migrated = 0;
|
||||
$errors = 0;
|
||||
|
||||
while ($doc = $adb->fetch_array($result)) {
|
||||
$notesid = $doc['notesid'];
|
||||
$title = $doc['title'];
|
||||
$oldS3Key = $doc['s3_key'];
|
||||
|
||||
echo "[$migrated/$total] Документ: $title (ID: $notesid)\n";
|
||||
echo " Старый s3_key: $oldS3Key\n";
|
||||
|
||||
// Извлекаем имя файла и расширение
|
||||
$oldFilename = basename($oldS3Key);
|
||||
$pathInfo = pathinfo($oldFilename);
|
||||
$extension = isset($pathInfo['extension']) ? '.' . $pathInfo['extension'] : '';
|
||||
|
||||
// Новое имя файла: file_docID_название.ext
|
||||
$newFilename = "file_{$notesid}_{$pathInfo['filename']}{$extension}";
|
||||
|
||||
// Новый путь в S3
|
||||
$newS3Key = "{$newFolder}/{$newFilename}";
|
||||
$newNcPath = "/crm/crm2/CRM_Active_Files/{$newFolder}/{$newFilename}";
|
||||
|
||||
// Новый URL
|
||||
$bucket = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
|
||||
$newUrl = "https://s3.twcstorage.ru/{$bucket}/crm2/CRM_Active_Files/{$newS3Key}";
|
||||
|
||||
echo " Новый s3_key: $newS3Key\n";
|
||||
echo " Новый nc_path: $newNcPath\n";
|
||||
|
||||
// Копируем файл в S3 (используем aws s3 cp)
|
||||
$oldKey = "crm2/CRM_Active_Files/" . $oldS3Key;
|
||||
$newKey = "crm2/CRM_Active_Files/" . $newS3Key;
|
||||
|
||||
$copyCmd = "aws s3 cp " .
|
||||
"s3://{$bucket}/{$oldKey} " .
|
||||
"s3://{$bucket}/{$newKey} " .
|
||||
"--endpoint-url https://s3.twcstorage.ru " .
|
||||
"--region ru-1 2>&1";
|
||||
|
||||
echo " Копирование в S3...\n";
|
||||
exec($copyCmd, $output, $returnCode);
|
||||
|
||||
if ($returnCode !== 0) {
|
||||
echo " ❌ ОШИБКА копирования: " . implode("\n", $output) . "\n";
|
||||
$errors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
echo " ✅ Скопировано в S3\n";
|
||||
|
||||
// Обновляем БД
|
||||
$updateResult = $adb->pquery(
|
||||
"UPDATE vtiger_notes
|
||||
SET s3_key = ?, nc_path = ?, filename = ?
|
||||
WHERE notesid = ?",
|
||||
[$newS3Key, $newNcPath, $newUrl, $notesid]
|
||||
);
|
||||
|
||||
if ($updateResult) {
|
||||
echo " ✅ БД обновлена\n";
|
||||
$migrated++;
|
||||
} else {
|
||||
echo " ❌ ОШИБКА обновления БД\n";
|
||||
$errors++;
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
echo "==========================================\n";
|
||||
echo "✅ МИГРАЦИЯ ЗАВЕРШЕНА!\n";
|
||||
echo "Обработано: $total\n";
|
||||
echo "Мигрировано: $migrated\n";
|
||||
echo "Ошибок: $errors\n";
|
||||
|
||||
170
crm_extensions/file_storage/n8n_nextcloud_activity_monitor.json
Normal file
170
crm_extensions/file_storage/n8n_nextcloud_activity_monitor.json
Normal file
@@ -0,0 +1,170 @@
|
||||
{
|
||||
"name": "Nextcloud Activity Monitor",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "seconds",
|
||||
"secondsInterval": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "schedule",
|
||||
"name": "Каждые 30 секунд",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [250, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://office.clientright.ru:8443/ocs/v2.php/apps/activity/api/v2/activity",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpBasicAuth",
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "format",
|
||||
"value": "json"
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"value": "100"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"value": "true"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "http-request",
|
||||
"name": "Get Nextcloud Activities",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4,
|
||||
"position": [450, 300],
|
||||
"credentials": {
|
||||
"httpBasicAuth": {
|
||||
"id": "3",
|
||||
"name": "Nextcloud Admin"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Получаем последний обработанный activity_id из Static Data\nconst staticData = getWorkflowStaticData('node');\nlet lastProcessedId = staticData.lastActivityId || 0;\n\nconst activities = $input.item.json.ocs.data;\nconst fileEvents = [];\n\n// Типы событий которые нас интересуют\nconst relevantTypes = ['file_created', 'file_changed', 'file_deleted', 'file_restored'];\n\n// Фильтруем и обрабатываем события\nfor (const activity of activities) {\n // Пропускаем уже обработанные\n if (activity.activity_id <= lastProcessedId) {\n continue;\n }\n \n // Только файловые события\n if (!relevantTypes.includes(activity.type)) {\n continue;\n }\n \n // РАЗБИВАЕМ агрегированные события!\n // Если есть objects - создаём событие для каждого файла\n if (activity.objects && typeof activity.objects === 'object') {\n const fileIds = Object.keys(activity.objects);\n \n for (const fileId of fileIds) {\n const filePath = activity.objects[fileId];\n \n fileEvents.push({\n json: {\n type: activity.type,\n source: 'nextcloud_activity',\n timestamp: activity.datetime,\n file_id: parseInt(fileId),\n path: filePath,\n filename: filePath ? filePath.split('/').pop() : null,\n user: activity.user,\n activity_id: activity.activity_id,\n action: activity.type.replace('file_', '')\n }\n });\n }\n } else {\n // Одиночное событие\n fileEvents.push({\n json: {\n type: activity.type,\n source: 'nextcloud_activity',\n timestamp: activity.datetime,\n file_id: activity.object_id,\n path: activity.object_name,\n filename: activity.object_name ? activity.object_name.split('/').pop() : null,\n user: activity.user,\n activity_id: activity.activity_id,\n action: activity.type.replace('file_', '')\n }\n });\n }\n \n // Обновляем последний ID\n if (activity.activity_id > lastProcessedId) {\n lastProcessedId = activity.activity_id;\n }\n}\n\n// Сохраняем последний обработанный ID\nstaticData.lastActivityId = lastProcessedId;\n\n// Если новых событий нет - возвращаем пустой массив\nif (fileEvents.length === 0) {\n return [];\n}\n\nreturn fileEvents;"
|
||||
},
|
||||
"id": "process-activities",
|
||||
"name": "Process & Filter Events",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [650, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"string": [
|
||||
{
|
||||
"value1": "={{$json.filename}}",
|
||||
"operation": "isNotEmpty"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "filter",
|
||||
"name": "Есть новые события?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [850, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "publish",
|
||||
"channel": "crm:file:events",
|
||||
"value": "={{JSON.stringify($json)}}",
|
||||
"keyType": "automatic"
|
||||
},
|
||||
"id": "redis-publish",
|
||||
"name": "Publish to Redis",
|
||||
"type": "n8n-nodes-base.redis",
|
||||
"typeVersion": 1,
|
||||
"position": [1050, 200],
|
||||
"credentials": {
|
||||
"redis": {
|
||||
"id": "2",
|
||||
"name": "CRM Redis"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"content": "=📢 Nextcloud Event:\n**Type:** {{$json.type}}\n**File:** {{$json.filename}}\n**Path:** {{$json.path}}\n**User:** {{$json.user}}\n**Time:** {{$json.timestamp}}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "logger",
|
||||
"name": "Log Event (optional)",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"typeVersion": 1,
|
||||
"position": [1050, 400]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"schedule": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "http-request",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"http-request": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "process-activities",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"process-activities": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "filter",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"filter": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "redis-publish",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
}
|
||||
}
|
||||
|
||||
174
crm_extensions/file_storage/n8n_s3_event_processor.json
Normal file
174
crm_extensions/file_storage/n8n_s3_event_processor.json
Normal file
@@ -0,0 +1,174 @@
|
||||
{
|
||||
"name": "S3 Event Processor",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "subscribe",
|
||||
"channel": "crm:file:events"
|
||||
},
|
||||
"id": "redis-subscribe",
|
||||
"name": "Redis Subscribe",
|
||||
"type": "n8n-nodes-base.redis",
|
||||
"typeVersion": 1,
|
||||
"position": [250, 300],
|
||||
"credentials": {
|
||||
"redis": {
|
||||
"id": "CRM_REDIS_ID",
|
||||
"name": "CRM Redis"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Парсим JSON из Redis message\nconst items = [];\n\nfor (const item of $input.all()) {\n const message = item.json.message;\n \n // Если message это строка - парсим\n let parsedMessage = message;\n if (typeof message === 'string') {\n parsedMessage = JSON.parse(message);\n }\n \n items.push({\n json: parsedMessage\n });\n}\n\nreturn items;"
|
||||
},
|
||||
"id": "parse-json",
|
||||
"name": "Parse Message",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [450, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.type }}",
|
||||
"rightValue": "file_created",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "equals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
}
|
||||
},
|
||||
"id": "filter-created",
|
||||
"name": "Только новые файлы",
|
||||
"type": "n8n-nodes-base.switch",
|
||||
"typeVersion": 3,
|
||||
"position": [650, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.filename }}",
|
||||
"rightValue": ".xlsx",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "endsWith"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "or"
|
||||
}
|
||||
},
|
||||
"id": "filter-xlsx",
|
||||
"name": "Только XLSX",
|
||||
"type": "n8n-nodes-base.switch",
|
||||
"typeVersion": 3,
|
||||
"position": [850, 200]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.filename }}",
|
||||
"rightValue": ".pdf",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "endsWith"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "or"
|
||||
}
|
||||
},
|
||||
"id": "filter-pdf",
|
||||
"name": "Только PDF",
|
||||
"type": "n8n-nodes-base.switch",
|
||||
"typeVersion": 3,
|
||||
"position": [850, 400]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"content": "=📊 **XLSX файл обнаружен!**\n\n**Имя:** {{ $json.filename }}\n**Путь:** {{ $json.path }}\n**Размер:** {{ $json.size }} bytes\n**Время:** {{ $json.timestamp }}\n\n---\n\nЗдесь можно:\n- Скачать файл из S3\n- Обработать данные\n- Загрузить в CRM\n- Отправить уведомление",
|
||||
"height": 312,
|
||||
"width": 389
|
||||
},
|
||||
"id": "process-xlsx",
|
||||
"name": "Обработка XLSX",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"typeVersion": 1,
|
||||
"position": [1050, 80]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"content": "=📄 **PDF файл обнаружен!**\n\n**Имя:** {{ $json.filename }}\n**Путь:** {{ $json.path }}\n**Размер:** {{ $json.size }} bytes\n**Время:** {{ $json.timestamp }}\n\n---\n\nЗдесь можно:\n- Скачать PDF из S3\n- OCR распознавание\n- Извлечь данные\n- Создать документ в CRM",
|
||||
"height": 312,
|
||||
"width": 389
|
||||
},
|
||||
"id": "process-pdf",
|
||||
"name": "Обработка PDF",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"typeVersion": 1,
|
||||
"position": [1050, 320]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"redis-subscribe": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "parse-json",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"parse-json": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "filter-created",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"filter-created": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "filter-xlsx",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "filter-pdf",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
}
|
||||
}
|
||||
|
||||
179
crm_extensions/file_storage/n8n_s3_monitor_workflow.json
Normal file
179
crm_extensions/file_storage/n8n_s3_monitor_workflow.json
Normal file
@@ -0,0 +1,179 @@
|
||||
{
|
||||
"name": "S3 File Monitor",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "seconds",
|
||||
"secondsInterval": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "schedule",
|
||||
"name": "Каждые 30 секунд",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [250, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "getAll",
|
||||
"bucketName": "={{$env.S3_BUCKET}}",
|
||||
"returnAll": true,
|
||||
"options": {
|
||||
"prefix": ""
|
||||
}
|
||||
},
|
||||
"id": "s3-list",
|
||||
"name": "List S3 Files",
|
||||
"type": "n8n-nodes-base.awsS3",
|
||||
"typeVersion": 1,
|
||||
"position": [450, 300],
|
||||
"credentials": {
|
||||
"aws": {
|
||||
"id": "1",
|
||||
"name": "TWC S3"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"functionCode": "// Храним состояние файлов в Static Data\nconst currentState = $node[\"s3-list\"].json;\nconst previousState = $getWorkflowStaticData('node') || {};\nconst events = [];\n\n// Проверяем новые и изменённые файлы\nfor (const file of currentState) {\n const key = file.Key;\n const etag = file.ETag;\n \n if (!previousState[key]) {\n // Новый файл\n events.push({\n type: 'file_created',\n source: 's3_monitor',\n timestamp: new Date().toISOString(),\n path: key,\n filename: key.split('/').pop(),\n size: file.Size,\n etag: etag,\n last_modified: file.LastModified\n });\n } else if (previousState[key].ETag !== etag) {\n // Файл изменён\n events.push({\n type: 'file_modified',\n source: 's3_monitor',\n timestamp: new Date().toISOString(),\n path: key,\n filename: key.split('/').pop(),\n size: file.Size,\n etag: etag,\n last_modified: file.LastModified\n });\n }\n \n previousState[key] = { ETag: etag, Size: file.Size };\n}\n\n// Проверяем удалённые файлы\nconst currentKeys = currentState.map(f => f.Key);\nfor (const key in previousState) {\n if (!currentKeys.includes(key)) {\n events.push({\n type: 'file_deleted',\n source: 's3_monitor',\n timestamp: new Date().toISOString(),\n path: key,\n filename: key.split('/').pop()\n });\n delete previousState[key];\n }\n}\n\n// Сохраняем новое состояние\n$setWorkflowStaticData(previousState);\n\nreturn events.map(event => ({ json: event }));"
|
||||
},
|
||||
"id": "detect-changes",
|
||||
"name": "Detect Changes",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 1,
|
||||
"position": [650, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.type }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "notEmpty"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
}
|
||||
},
|
||||
"id": "filter",
|
||||
"name": "Есть изменения?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [850, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "publish",
|
||||
"channel": "crm:file:events",
|
||||
"value": "={{JSON.stringify($json)}}",
|
||||
"keyType": "automatic",
|
||||
"expire": false,
|
||||
"expireAfter": 86400
|
||||
},
|
||||
"id": "redis-publish",
|
||||
"name": "Publish to Redis",
|
||||
"type": "n8n-nodes-base.redis",
|
||||
"typeVersion": 1,
|
||||
"position": [1050, 200],
|
||||
"credentials": {
|
||||
"redis": {
|
||||
"id": "2",
|
||||
"name": "CRM Redis"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "={{$env.WEBHOOK_URL}}",
|
||||
"options": {
|
||||
"bodyContentType": "json"
|
||||
},
|
||||
"sendBody": true,
|
||||
"bodyParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "event",
|
||||
"value": "={{$json}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "webhook",
|
||||
"name": "Send Webhook (опционально)",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 3,
|
||||
"position": [1050, 400]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"schedule": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "s3-list",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"s3-list": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "detect-changes",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"detect-changes": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "filter",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"filter": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "redis-publish",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "webhook",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
}
|
||||
}
|
||||
|
||||
249
crm_extensions/file_storage/nextcloud_activity_monitor.js
Executable file
249
crm_extensions/file_storage/nextcloud_activity_monitor.js
Executable file
@@ -0,0 +1,249 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Nextcloud Activity Monitor
|
||||
* Мониторит события файлов через Activity API и публикует в Redis
|
||||
*/
|
||||
|
||||
const https = require('https');
|
||||
const Redis = require('ioredis');
|
||||
|
||||
const CONFIG = {
|
||||
nextcloud: {
|
||||
host: 'office.clientright.ru',
|
||||
port: 8443,
|
||||
username: 'admin',
|
||||
password: 'tGHKS-3cC9m-7Hggb-65Awk-zxWQE',
|
||||
auth: Buffer.from('admin:tGHKS-3cC9m-7Hggb-65Awk-zxWQE').toString('base64')
|
||||
},
|
||||
redis: {
|
||||
host: '147.45.146.17',
|
||||
port: 6379,
|
||||
password: 'CRM_Redis_Pass_2025_Secure!'
|
||||
},
|
||||
pollInterval: 30000, // 30 секунд
|
||||
stateKey: 'crm:nextcloud:activity:state'
|
||||
};
|
||||
|
||||
const redis = new Redis({
|
||||
host: CONFIG.redis.host,
|
||||
port: CONFIG.redis.port,
|
||||
password: CONFIG.redis.password
|
||||
});
|
||||
|
||||
// Хранилище последнего обработанного activity_id
|
||||
let lastActivityId = 0;
|
||||
|
||||
// Загрузка состояния из Redis
|
||||
async function loadState() {
|
||||
try {
|
||||
const data = await redis.get(CONFIG.stateKey);
|
||||
if (data) {
|
||||
const state = JSON.parse(data);
|
||||
lastActivityId = state.lastActivityId || 0;
|
||||
console.log(`📥 Последний обработанный activity_id: ${lastActivityId}`);
|
||||
} else {
|
||||
console.log('📥 Состояние пустое (первый запуск)');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('⚠️ Ошибка загрузки состояния:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Сохранение состояния в Redis
|
||||
async function saveState() {
|
||||
try {
|
||||
await redis.set(CONFIG.stateKey, JSON.stringify({ lastActivityId }));
|
||||
} catch (err) {
|
||||
console.error('⚠️ Ошибка сохранения состояния:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Получение активностей из Nextcloud API
|
||||
function getActivities(limit = 100) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
hostname: CONFIG.nextcloud.host,
|
||||
port: CONFIG.nextcloud.port,
|
||||
path: `/ocs/v2.php/apps/activity/api/v2/activity?format=json&limit=${limit}`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Basic ${CONFIG.nextcloud.auth}`,
|
||||
'OCS-APIRequest': 'true'
|
||||
},
|
||||
rejectUnauthorized: false
|
||||
};
|
||||
|
||||
const req = https.request(options, (res) => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const json = JSON.parse(data);
|
||||
resolve(json.ocs.data || []);
|
||||
} catch (err) {
|
||||
reject(new Error('Ошибка парсинга JSON: ' + err.message));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
req.setTimeout(10000, () => {
|
||||
req.destroy();
|
||||
reject(new Error('Timeout'));
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// Публикация события в Redis
|
||||
async function publishEvent(event) {
|
||||
console.log(`\n 📢 ${event.type.toUpperCase()}: ${event.filename}`);
|
||||
console.log(` 🆔 file_id: ${event.file_id}`);
|
||||
console.log(` 👤 user: ${event.user}`);
|
||||
|
||||
try {
|
||||
await redis.publish('crm:file:events', JSON.stringify(event));
|
||||
console.log(` ✅ Опубликовано в Redis`);
|
||||
} catch (err) {
|
||||
console.error(` ❌ Ошибка публикации:`, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Сканирование новых активностей
|
||||
async function scanActivities() {
|
||||
try {
|
||||
console.log(`\n🔍 Проверка новых событий... (${new Date().toISOString()})`);
|
||||
|
||||
const activities = await getActivities(100);
|
||||
|
||||
const relevantTypes = ['file_created', 'file_changed', 'file_deleted', 'file_restored'];
|
||||
let newEvents = 0;
|
||||
let totalFiles = 0;
|
||||
|
||||
// Обрабатываем активности (от новых к старым)
|
||||
for (const activity of activities) {
|
||||
// Пропускаем уже обработанные
|
||||
if (activity.activity_id <= lastActivityId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Только файловые события
|
||||
if (!relevantTypes.includes(activity.type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
newEvents++;
|
||||
|
||||
// РАЗБИВАЕМ агрегированные события на отдельные файлы!
|
||||
if (activity.objects && typeof activity.objects === 'object') {
|
||||
// Множественное событие - разбиваем
|
||||
const fileIds = Object.keys(activity.objects);
|
||||
totalFiles += fileIds.length;
|
||||
|
||||
console.log(` 📦 Агрегированное событие: ${fileIds.length} файлов`);
|
||||
|
||||
for (const fileId of fileIds) {
|
||||
const filePath = activity.objects[fileId];
|
||||
|
||||
const event = {
|
||||
type: activity.type,
|
||||
source: 'nextcloud_activity',
|
||||
timestamp: activity.datetime,
|
||||
file_id: parseInt(fileId),
|
||||
path: filePath,
|
||||
filename: filePath ? filePath.split('/').pop().replace(/^\//, '') : null,
|
||||
user: activity.user,
|
||||
activity_id: activity.activity_id,
|
||||
action: activity.type.replace('file_', '')
|
||||
};
|
||||
|
||||
await publishEvent(event);
|
||||
}
|
||||
} else {
|
||||
// Одиночное событие
|
||||
totalFiles++;
|
||||
|
||||
const event = {
|
||||
type: activity.type,
|
||||
source: 'nextcloud_activity',
|
||||
timestamp: activity.datetime,
|
||||
file_id: activity.object_id,
|
||||
path: activity.object_name,
|
||||
filename: activity.object_name ? activity.object_name.split('/').pop().replace(/^\//, '') : null,
|
||||
user: activity.user,
|
||||
activity_id: activity.activity_id,
|
||||
action: activity.type.replace('file_', '')
|
||||
};
|
||||
|
||||
await publishEvent(event);
|
||||
}
|
||||
|
||||
// Обновляем последний ID
|
||||
if (activity.activity_id > lastActivityId) {
|
||||
lastActivityId = activity.activity_id;
|
||||
}
|
||||
}
|
||||
|
||||
// Сохраняем состояние
|
||||
await saveState();
|
||||
|
||||
console.log(`\n✅ Сканирование завершено:`);
|
||||
console.log(` 📊 Новых активностей: ${newEvents}`);
|
||||
console.log(` 📁 Файлов обработано: ${totalFiles}`);
|
||||
console.log(` 🆔 Последний activity_id: ${lastActivityId}`);
|
||||
|
||||
} catch (err) {
|
||||
console.error('❌ Ошибка сканирования:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Запуск
|
||||
async function start() {
|
||||
console.log('🚀 Nextcloud Activity Monitor');
|
||||
console.log('════════════════════════════════════════════════════════════════════════════════');
|
||||
console.log(`📡 Nextcloud: ${CONFIG.nextcloud.host}:${CONFIG.nextcloud.port}`);
|
||||
console.log(`📡 Redis: ${CONFIG.redis.host}:${CONFIG.redis.port}`);
|
||||
console.log(`🔄 Интервал: ${CONFIG.pollInterval / 1000}с`);
|
||||
console.log('════════════════════════════════════════════════════════════════════════════════\n');
|
||||
|
||||
await loadState();
|
||||
|
||||
console.log('👂 Начинаем мониторинг...\n');
|
||||
|
||||
// Первое сканирование
|
||||
await scanActivities();
|
||||
|
||||
// Периодическое сканирование
|
||||
setInterval(scanActivities, CONFIG.pollInterval);
|
||||
}
|
||||
|
||||
// Запуск при подключении к Redis
|
||||
redis.on('connect', () => {
|
||||
console.log('✅ Подключились к Redis\n');
|
||||
start();
|
||||
});
|
||||
|
||||
redis.on('error', (err) => {
|
||||
console.error('❌ Redis ошибка:', err.message);
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\n\n⛔ Остановка мониторинга...');
|
||||
redis.disconnect();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
console.log('\n\n⛔ Получен сигнал SIGTERM, останавливаемся...');
|
||||
redis.disconnect();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
124
crm_extensions/file_storage/nextcloud_cache_updater.js
Executable file
124
crm_extensions/file_storage/nextcloud_cache_updater.js
Executable file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Nextcloud Cache Updater
|
||||
*
|
||||
* Подписывается на Redis канал crm:file:events
|
||||
* При новом файле - обновляет кеш Nextcloud для этого файла
|
||||
* БЕЗ полного сканирования всей папки!
|
||||
*/
|
||||
|
||||
const Redis = require('ioredis');
|
||||
const { exec } = require('child_process');
|
||||
const util = require('util');
|
||||
const execPromise = util.promisify(exec);
|
||||
|
||||
const CONFIG = {
|
||||
redis: {
|
||||
host: '147.45.146.17',
|
||||
port: 6379,
|
||||
password: 'CRM_Redis_Pass_2025_Secure!'
|
||||
},
|
||||
channel: 'crm:file:events',
|
||||
nextcloudContainer: 'nextcloud-fresh'
|
||||
};
|
||||
|
||||
const redis = new Redis(CONFIG.redis);
|
||||
|
||||
console.log('🔄 Nextcloud Cache Updater');
|
||||
console.log('==========================================');
|
||||
console.log(`📡 Подписка на: ${CONFIG.channel}`);
|
||||
console.log(`🐳 Nextcloud: ${CONFIG.nextcloudContainer}`);
|
||||
console.log('');
|
||||
|
||||
// Подписка на канал
|
||||
redis.subscribe(CONFIG.channel, (err, count) => {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка подписки:', err);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`✅ Подписка активна (${count} каналов)`);
|
||||
console.log('⏳ Ожидание событий...\n');
|
||||
});
|
||||
|
||||
// Обработка событий
|
||||
redis.on('message', async (channel, message) => {
|
||||
try {
|
||||
const event = JSON.parse(message);
|
||||
|
||||
// Логируем событие
|
||||
const timestamp = new Date().toISOString();
|
||||
console.log(`[${timestamp}] 📥 Событие:`);
|
||||
console.log(` Type: ${event.type}`);
|
||||
console.log(` Source: ${event.source}`);
|
||||
console.log(` Path: ${event.path || event.filename}`);
|
||||
|
||||
// Обрабатываем только создание/изменение файлов
|
||||
if (!['file_created', 'file_modified', 'file_update'].includes(event.type)) {
|
||||
console.log(` ⏭️ Пропуск (не файловое событие)\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Извлекаем путь файла
|
||||
let filePath = event.path || event.filename;
|
||||
|
||||
// Для событий из S3 Monitor - путь уже правильный
|
||||
// Для событий из Nextcloud - может быть без префикса
|
||||
|
||||
// Формируем путь для Nextcloud
|
||||
let ncPath = filePath;
|
||||
if (!ncPath.startsWith('/')) {
|
||||
ncPath = '/admin/files/crm/' + ncPath;
|
||||
}
|
||||
|
||||
console.log(` 🔄 Обновление кеша Nextcloud...`);
|
||||
console.log(` Путь: ${ncPath}`);
|
||||
|
||||
// Обновляем кеш только для этого файла
|
||||
const command = `docker exec -u www-data ${CONFIG.nextcloudContainer} php occ files:scan --path="${ncPath}" 2>&1`;
|
||||
|
||||
try {
|
||||
const { stdout, stderr } = await execPromise(command);
|
||||
|
||||
if (stderr && !stderr.includes('Starting scan')) {
|
||||
console.log(` ⚠️ Предупреждение: ${stderr}`);
|
||||
}
|
||||
|
||||
console.log(` ✅ Кеш обновлён`);
|
||||
|
||||
// Дополнительно очищаем statcache для этой папки
|
||||
const dirname = ncPath.substring(0, ncPath.lastIndexOf('/'));
|
||||
const clearCommand = `docker exec -u www-data ${CONFIG.nextcloudContainer} php occ files:scan --path="${dirname}" --shallow 2>&1`;
|
||||
await execPromise(clearCommand);
|
||||
|
||||
console.log(` ✅ Родительская папка обновлена\n`);
|
||||
|
||||
} catch (execError) {
|
||||
console.error(` ❌ Ошибка обновления: ${execError.message}\n`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Ошибка обработки события:`, error.message);
|
||||
console.error(` Сообщение:`, message.substring(0, 200));
|
||||
console.log('');
|
||||
}
|
||||
});
|
||||
|
||||
// Обработка ошибок
|
||||
redis.on('error', (err) => {
|
||||
console.error('❌ Redis ошибка:', err);
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\n\n🛑 Остановка...');
|
||||
redis.disconnect();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
console.log('\n\n🛑 Остановка...');
|
||||
redis.disconnect();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
|
||||
130
crm_extensions/file_storage/nextcloud_fileid_indexer.js
Executable file
130
crm_extensions/file_storage/nextcloud_fileid_indexer.js
Executable file
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Nextcloud FileID Indexer
|
||||
*
|
||||
* Индексирует fileId из Nextcloud БД в Redis для быстрого доступа
|
||||
* Структура: crm:nc:fileid:{path} => fileId
|
||||
*/
|
||||
|
||||
const mysql = require('mysql2/promise');
|
||||
const Redis = require('ioredis');
|
||||
|
||||
const CONFIG = {
|
||||
nextcloud_db: {
|
||||
host: '192.168.128.3',
|
||||
user: 'nextcloud',
|
||||
password: 'nextcloud_password',
|
||||
database: 'nextcloud'
|
||||
},
|
||||
redis: {
|
||||
host: '147.45.146.17',
|
||||
port: 6379,
|
||||
password: 'CRM_Redis_Pass_2025_Secure!'
|
||||
},
|
||||
// Индексируем только файлы из этих папок
|
||||
pathPrefixes: [
|
||||
'files/crm/crm2/',
|
||||
'files/crm/erv_app/'
|
||||
],
|
||||
indexInterval: 60000 // Обновляем индекс каждую минуту
|
||||
};
|
||||
|
||||
const redis = new Redis(CONFIG.redis);
|
||||
let connection = null;
|
||||
|
||||
async function connectDB() {
|
||||
try {
|
||||
connection = await mysql.createConnection(CONFIG.nextcloud_db);
|
||||
console.log('✅ Подключились к БД Nextcloud');
|
||||
} catch (err) {
|
||||
console.error('❌ Ошибка подключения к БД:', err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function indexFiles() {
|
||||
try {
|
||||
console.log(`\n🔍 Индексация файлов... (${new Date().toISOString()})`);
|
||||
|
||||
let totalIndexed = 0;
|
||||
|
||||
for (const prefix of CONFIG.pathPrefixes) {
|
||||
// Получаем все файлы из этой папки
|
||||
const [rows] = await connection.execute(
|
||||
'SELECT fileid, path, name, size, mtime FROM oc_filecache WHERE path LIKE ? AND mimetype != 2',
|
||||
[prefix + '%']
|
||||
);
|
||||
|
||||
console.log(` 📁 ${prefix}: найдено ${rows.length} файлов`);
|
||||
|
||||
// Индексируем в Redis
|
||||
const pipeline = redis.pipeline();
|
||||
|
||||
for (const row of rows) {
|
||||
const key = `crm:nc:fileid:${row.path}`;
|
||||
const value = JSON.stringify({
|
||||
fileId: row.fileid,
|
||||
name: row.name,
|
||||
size: row.size,
|
||||
mtime: row.mtime
|
||||
});
|
||||
|
||||
// Храним 24 часа (обновляется каждую минуту)
|
||||
pipeline.setex(key, 86400, value);
|
||||
totalIndexed++;
|
||||
}
|
||||
|
||||
await pipeline.exec();
|
||||
}
|
||||
|
||||
console.log(`✅ Проиндексировано: ${totalIndexed} файлов`);
|
||||
|
||||
} catch (err) {
|
||||
console.error('❌ Ошибка индексации:', err.message);
|
||||
console.error(err.stack);
|
||||
}
|
||||
}
|
||||
|
||||
async function start() {
|
||||
console.log('🚀 Nextcloud FileID Indexer');
|
||||
console.log('════════════════════════════════════════════════════════════════════════════════');
|
||||
console.log(`🗄️ Nextcloud DB: ${CONFIG.nextcloud_db.host}/${CONFIG.nextcloud_db.database}`);
|
||||
console.log(`📡 Redis: ${CONFIG.redis.host}:${CONFIG.redis.port}`);
|
||||
console.log(`🔄 Интервал: ${CONFIG.indexInterval / 1000}с`);
|
||||
console.log('════════════════════════════════════════════════════════════════════════════════\n');
|
||||
|
||||
await connectDB();
|
||||
|
||||
console.log('👂 Начинаем индексацию...\n');
|
||||
|
||||
// Первая индексация
|
||||
await indexFiles();
|
||||
|
||||
// Периодическая индексация
|
||||
setInterval(indexFiles, CONFIG.indexInterval);
|
||||
}
|
||||
|
||||
// Запуск
|
||||
redis.on('connect', () => {
|
||||
console.log('✅ Подключились к Redis\n');
|
||||
start();
|
||||
});
|
||||
|
||||
redis.on('error', (err) => {
|
||||
console.error('❌ Redis ошибка:', err.message);
|
||||
});
|
||||
|
||||
process.on('SIGINT', async () => {
|
||||
console.log('\n\n⛔ Остановка индексатора...');
|
||||
if (connection) await connection.end();
|
||||
redis.disconnect();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
console.log('\n\n⛔ Остановка индексатора...');
|
||||
if (connection) await connection.end();
|
||||
redis.disconnect();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
1
crm_extensions/file_storage/node_modules/.bin/fxparser
generated
vendored
Symbolic link
1
crm_extensions/file_storage/node_modules/.bin/fxparser
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../fast-xml-parser/src/cli/cli.js
|
||||
1870
crm_extensions/file_storage/node_modules/.package-lock.json
generated
vendored
Normal file
1870
crm_extensions/file_storage/node_modules/.package-lock.json
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
100
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/CHANGELOG.md
generated
vendored
Normal file
100
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [5.2.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v5.1.0...v5.2.0) (2023-10-16)
|
||||
|
||||
### Features
|
||||
|
||||
- support ESM artifacts in all packages ([#752](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/752)) ([e930ffb](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/e930ffba5cfef66dd242049e7d514ced232c1e3b))
|
||||
|
||||
# [5.1.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v5.0.0...v5.1.0) (2023-09-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Update tsc to 2.x ([#735](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/735)) ([782e0de](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/782e0de9f5fef41f694130580a69d940894b6b8c))
|
||||
|
||||
### Features
|
||||
|
||||
- Use @smithy/util-utf8 ([#730](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/730)) ([00fb851](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/00fb851ca3559d5a1f370f9256814de1210826b8)), closes [#699](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/699)
|
||||
|
||||
# [5.0.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v4.0.1...v5.0.0) (2023-07-13)
|
||||
|
||||
**Note:** Version bump only for package @aws-crypto/crc32
|
||||
|
||||
# [4.0.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v3.0.0...v4.0.0) (2023-02-20)
|
||||
|
||||
**Note:** Version bump only for package @aws-crypto/crc32
|
||||
|
||||
# [3.0.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v2.0.2...v3.0.0) (2023-01-12)
|
||||
|
||||
- feat!: replace Hash implementations with Checksum interface (#492) ([da43dc0](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/da43dc0fdf669d9ebb5bfb1b1f7c79e46c4aaae1)), closes [#492](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/492)
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
- All classes that implemented `Hash` now implement `Checksum`.
|
||||
|
||||
## [2.0.2](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v2.0.1...v2.0.2) (2022-09-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **#337:** update @aws-sdk/types ([#373](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/373)) ([b26a811](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/b26a811a392f5209c7ec7e57251500d4d78f97ff)), closes [#337](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/337)
|
||||
|
||||
## [2.0.1](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v2.0.0...v2.0.1) (2021-12-09)
|
||||
|
||||
**Note:** Version bump only for package @aws-crypto/crc32
|
||||
|
||||
# [2.0.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v1.2.2...v2.0.0) (2021-10-25)
|
||||
|
||||
**Note:** Version bump only for package @aws-crypto/crc32
|
||||
|
||||
## [1.2.2](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v1.2.1...v1.2.2) (2021-10-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **crc32c:** ie11 does not support Array.from ([#221](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/221)) ([5f49547](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/5f495472ab8988cf203e0f2a70a51f7e1fcd7e60))
|
||||
|
||||
## [1.2.1](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v1.2.0...v1.2.1) (2021-09-17)
|
||||
|
||||
**Note:** Version bump only for package @aws-crypto/crc32
|
||||
|
||||
# [1.2.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v1.1.1...v1.2.0) (2021-09-17)
|
||||
|
||||
### Features
|
||||
|
||||
- Add AwsCrc32 Hash ([f5d7e81](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/f5d7e815fcbe0f8da1edb855fea3bd33eb1edc15))
|
||||
|
||||
# [1.1.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/@aws-crypto/crc32@1.0.0...@aws-crypto/crc32@1.1.0) (2021-08-11)
|
||||
|
||||
### Features
|
||||
|
||||
- Create CRC-32C implementation ([#201](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/201)) ([e43c7ec](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/e43c7ecd30d6499fa696f5839ecc30502a34b8b6))
|
||||
|
||||
# [1.0.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/@aws-crypto/crc32@1.0.0-alpha.0...@aws-crypto/crc32@1.0.0) (2020-10-22)
|
||||
|
||||
**Note:** Version bump only for package @aws-crypto/crc32
|
||||
|
||||
# [1.0.0-alpha.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/@aws-crypto/crc32@0.1.0-preview.4...@aws-crypto/crc32@1.0.0-alpha.0) (2020-02-07)
|
||||
|
||||
**Note:** Version bump only for package @aws-crypto/crc32
|
||||
|
||||
# [0.1.0-preview.4](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/@aws-crypto/crc32@0.1.0-preview.2...@aws-crypto/crc32@0.1.0-preview.4) (2020-01-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Changed package.json files to point to the right Git repo ([#9](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/9)) ([028245d](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/028245d72e642ca98d82226afb300eb154503c4a)), closes [#8](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/8)
|
||||
- lerna version maintains package-lock ([#14](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/14)) ([2ef29e1](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/2ef29e13779703a5c9b32e93d18918fcb33b7272)), closes [#13](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/13)
|
||||
|
||||
# [0.1.0-preview.3](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/@aws-crypto/crc32@0.1.0-preview.2...@aws-crypto/crc32@0.1.0-preview.3) (2019-11-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Changed package.json files to point to the right Git repo ([#9](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/9)) ([028245d](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/028245d72e642ca98d82226afb300eb154503c4a)), closes [#8](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/8)
|
||||
- lerna version maintains package-lock ([#14](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/14)) ([2ef29e1](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/2ef29e13779703a5c9b32e93d18918fcb33b7272)), closes [#13](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/13)
|
||||
|
||||
# [0.1.0-preview.2](https://github.com/aws/aws-javascript-crypto-helpers/compare/@aws-crypto/crc32@0.1.0-preview.1...@aws-crypto/crc32@0.1.0-preview.2) (2019-10-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- remove /src/ from .npmignore (for sourcemaps) ([#5](https://github.com/aws/aws-javascript-crypto-helpers/issues/5)) ([ec52056](https://github.com/aws/aws-javascript-crypto-helpers/commit/ec52056))
|
||||
201
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/LICENSE
generated
vendored
Normal file
201
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
16
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/README.md
generated
vendored
Normal file
16
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/README.md
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# @aws-crypto/crc32
|
||||
|
||||
Pure JS implementation of CRC32 https://en.wikipedia.org/wiki/Cyclic_redundancy_check
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
import { Crc32 } from '@aws-crypto/crc32';
|
||||
|
||||
const crc32Digest = (new Crc32).update(buffer).digest()
|
||||
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
`npm test`
|
||||
7
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/main/aws_crc32.d.ts
generated
vendored
Normal file
7
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/main/aws_crc32.d.ts
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import { SourceData, Checksum } from "@aws-sdk/types";
|
||||
export declare class AwsCrc32 implements Checksum {
|
||||
private crc32;
|
||||
update(toHash: SourceData): void;
|
||||
digest(): Promise<Uint8Array>;
|
||||
reset(): void;
|
||||
}
|
||||
31
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/main/aws_crc32.js
generated
vendored
Normal file
31
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/main/aws_crc32.js
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
"use strict";
|
||||
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AwsCrc32 = void 0;
|
||||
var tslib_1 = require("tslib");
|
||||
var util_1 = require("@aws-crypto/util");
|
||||
var index_1 = require("./index");
|
||||
var AwsCrc32 = /** @class */ (function () {
|
||||
function AwsCrc32() {
|
||||
this.crc32 = new index_1.Crc32();
|
||||
}
|
||||
AwsCrc32.prototype.update = function (toHash) {
|
||||
if ((0, util_1.isEmptyData)(toHash))
|
||||
return;
|
||||
this.crc32.update((0, util_1.convertToBuffer)(toHash));
|
||||
};
|
||||
AwsCrc32.prototype.digest = function () {
|
||||
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
||||
return tslib_1.__generator(this, function (_a) {
|
||||
return [2 /*return*/, (0, util_1.numToUint8)(this.crc32.digest())];
|
||||
});
|
||||
});
|
||||
};
|
||||
AwsCrc32.prototype.reset = function () {
|
||||
this.crc32 = new index_1.Crc32();
|
||||
};
|
||||
return AwsCrc32;
|
||||
}());
|
||||
exports.AwsCrc32 = AwsCrc32;
|
||||
//# sourceMappingURL=aws_crc32.js.map
|
||||
1
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/main/aws_crc32.js.map
generated
vendored
Normal file
1
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/main/aws_crc32.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"aws_crc32.js","sourceRoot":"","sources":["../../src/aws_crc32.ts"],"names":[],"mappings":";AAAA,oEAAoE;AACpE,sCAAsC;;;;AAGtC,yCAA4E;AAC5E,iCAAgC;AAEhC;IAAA;QACU,UAAK,GAAG,IAAI,aAAK,EAAE,CAAC;IAe9B,CAAC;IAbC,yBAAM,GAAN,UAAO,MAAkB;QACvB,IAAI,IAAA,kBAAW,EAAC,MAAM,CAAC;YAAE,OAAO;QAEhC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAA,sBAAe,EAAC,MAAM,CAAC,CAAC,CAAC;IAC7C,CAAC;IAEK,yBAAM,GAAZ;;;gBACE,sBAAO,IAAA,iBAAU,EAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAC;;;KACxC;IAED,wBAAK,GAAL;QACE,IAAI,CAAC,KAAK,GAAG,IAAI,aAAK,EAAE,CAAC;IAC3B,CAAC;IACH,eAAC;AAAD,CAAC,AAhBD,IAgBC;AAhBY,4BAAQ"}
|
||||
7
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/main/index.d.ts
generated
vendored
Normal file
7
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/main/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export declare function crc32(data: Uint8Array): number;
|
||||
export declare class Crc32 {
|
||||
private checksum;
|
||||
update(data: Uint8Array): this;
|
||||
digest(): number;
|
||||
}
|
||||
export { AwsCrc32 } from "./aws_crc32";
|
||||
108
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/main/index.js
generated
vendored
Normal file
108
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/main/index.js
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AwsCrc32 = exports.Crc32 = exports.crc32 = void 0;
|
||||
var tslib_1 = require("tslib");
|
||||
var util_1 = require("@aws-crypto/util");
|
||||
function crc32(data) {
|
||||
return new Crc32().update(data).digest();
|
||||
}
|
||||
exports.crc32 = crc32;
|
||||
var Crc32 = /** @class */ (function () {
|
||||
function Crc32() {
|
||||
this.checksum = 0xffffffff;
|
||||
}
|
||||
Crc32.prototype.update = function (data) {
|
||||
var e_1, _a;
|
||||
try {
|
||||
for (var data_1 = tslib_1.__values(data), data_1_1 = data_1.next(); !data_1_1.done; data_1_1 = data_1.next()) {
|
||||
var byte = data_1_1.value;
|
||||
this.checksum =
|
||||
(this.checksum >>> 8) ^ lookupTable[(this.checksum ^ byte) & 0xff];
|
||||
}
|
||||
}
|
||||
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (data_1_1 && !data_1_1.done && (_a = data_1.return)) _a.call(data_1);
|
||||
}
|
||||
finally { if (e_1) throw e_1.error; }
|
||||
}
|
||||
return this;
|
||||
};
|
||||
Crc32.prototype.digest = function () {
|
||||
return (this.checksum ^ 0xffffffff) >>> 0;
|
||||
};
|
||||
return Crc32;
|
||||
}());
|
||||
exports.Crc32 = Crc32;
|
||||
// prettier-ignore
|
||||
var a_lookUpTable = [
|
||||
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
|
||||
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
|
||||
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
|
||||
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
|
||||
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
|
||||
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
|
||||
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
|
||||
0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
|
||||
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
|
||||
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
|
||||
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
|
||||
0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
|
||||
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
|
||||
0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
|
||||
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
|
||||
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
|
||||
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
|
||||
0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
|
||||
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
|
||||
0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
|
||||
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
|
||||
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
|
||||
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
|
||||
0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
|
||||
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
|
||||
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
|
||||
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
|
||||
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
|
||||
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
|
||||
0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
|
||||
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
|
||||
0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
|
||||
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
|
||||
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
|
||||
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
|
||||
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
|
||||
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
|
||||
0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
|
||||
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
|
||||
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
|
||||
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
|
||||
0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
|
||||
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
|
||||
0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
|
||||
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
|
||||
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
|
||||
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
|
||||
0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
|
||||
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
|
||||
0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
|
||||
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
|
||||
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
|
||||
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
|
||||
0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
|
||||
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
|
||||
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
|
||||
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
|
||||
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
|
||||
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
|
||||
0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
|
||||
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
|
||||
0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
|
||||
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
|
||||
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D,
|
||||
];
|
||||
var lookupTable = (0, util_1.uint32ArrayFrom)(a_lookUpTable);
|
||||
var aws_crc32_1 = require("./aws_crc32");
|
||||
Object.defineProperty(exports, "AwsCrc32", { enumerable: true, get: function () { return aws_crc32_1.AwsCrc32; } });
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/main/index.js.map
generated
vendored
Normal file
1
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/main/index.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;AAAA,yCAAiD;AAEjD,SAAgB,KAAK,CAAC,IAAgB;IACpC,OAAO,IAAI,KAAK,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;AAC3C,CAAC;AAFD,sBAEC;AAED;IAAA;QACU,aAAQ,GAAG,UAAU,CAAC;IAchC,CAAC;IAZC,sBAAM,GAAN,UAAO,IAAgB;;;YACrB,KAAmB,IAAA,SAAA,iBAAA,IAAI,CAAA,0BAAA,4CAAE;gBAApB,IAAM,IAAI,iBAAA;gBACb,IAAI,CAAC,QAAQ;oBACX,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;aACtE;;;;;;;;;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED,sBAAM,GAAN;QACE,OAAO,CAAC,IAAI,CAAC,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC;IACH,YAAC;AAAD,CAAC,AAfD,IAeC;AAfY,sBAAK;AAiBlB,kBAAkB;AAClB,IAAM,aAAa,GAAG;IACpB,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;CAC/C,CAAC;AACF,IAAM,WAAW,GAAgB,IAAA,sBAAe,EAAC,aAAa,CAAC,CAAA;AAC/D,yCAAuC;AAA9B,qGAAA,QAAQ,OAAA"}
|
||||
7
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/module/aws_crc32.d.ts
generated
vendored
Normal file
7
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/module/aws_crc32.d.ts
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import { SourceData, Checksum } from "@aws-sdk/types";
|
||||
export declare class AwsCrc32 implements Checksum {
|
||||
private crc32;
|
||||
update(toHash: SourceData): void;
|
||||
digest(): Promise<Uint8Array>;
|
||||
reset(): void;
|
||||
}
|
||||
28
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/module/aws_crc32.js
generated
vendored
Normal file
28
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/module/aws_crc32.js
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import { __awaiter, __generator } from "tslib";
|
||||
import { convertToBuffer, isEmptyData, numToUint8 } from "@aws-crypto/util";
|
||||
import { Crc32 } from "./index";
|
||||
var AwsCrc32 = /** @class */ (function () {
|
||||
function AwsCrc32() {
|
||||
this.crc32 = new Crc32();
|
||||
}
|
||||
AwsCrc32.prototype.update = function (toHash) {
|
||||
if (isEmptyData(toHash))
|
||||
return;
|
||||
this.crc32.update(convertToBuffer(toHash));
|
||||
};
|
||||
AwsCrc32.prototype.digest = function () {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
return __generator(this, function (_a) {
|
||||
return [2 /*return*/, numToUint8(this.crc32.digest())];
|
||||
});
|
||||
});
|
||||
};
|
||||
AwsCrc32.prototype.reset = function () {
|
||||
this.crc32 = new Crc32();
|
||||
};
|
||||
return AwsCrc32;
|
||||
}());
|
||||
export { AwsCrc32 };
|
||||
//# sourceMappingURL=aws_crc32.js.map
|
||||
1
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/module/aws_crc32.js.map
generated
vendored
Normal file
1
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/module/aws_crc32.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"aws_crc32.js","sourceRoot":"","sources":["../../src/aws_crc32.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,sCAAsC;;AAGtC,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC;IAAA;QACU,UAAK,GAAG,IAAI,KAAK,EAAE,CAAC;IAe9B,CAAC;IAbC,yBAAM,GAAN,UAAO,MAAkB;QACvB,IAAI,WAAW,CAAC,MAAM,CAAC;YAAE,OAAO;QAEhC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7C,CAAC;IAEK,yBAAM,GAAZ;;;gBACE,sBAAO,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAC;;;KACxC;IAED,wBAAK,GAAL;QACE,IAAI,CAAC,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC;IAC3B,CAAC;IACH,eAAC;AAAD,CAAC,AAhBD,IAgBC"}
|
||||
7
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/module/index.d.ts
generated
vendored
Normal file
7
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/module/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export declare function crc32(data: Uint8Array): number;
|
||||
export declare class Crc32 {
|
||||
private checksum;
|
||||
update(data: Uint8Array): this;
|
||||
digest(): number;
|
||||
}
|
||||
export { AwsCrc32 } from "./aws_crc32";
|
||||
103
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/module/index.js
generated
vendored
Normal file
103
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/module/index.js
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
import { __values } from "tslib";
|
||||
import { uint32ArrayFrom } from "@aws-crypto/util";
|
||||
export function crc32(data) {
|
||||
return new Crc32().update(data).digest();
|
||||
}
|
||||
var Crc32 = /** @class */ (function () {
|
||||
function Crc32() {
|
||||
this.checksum = 0xffffffff;
|
||||
}
|
||||
Crc32.prototype.update = function (data) {
|
||||
var e_1, _a;
|
||||
try {
|
||||
for (var data_1 = __values(data), data_1_1 = data_1.next(); !data_1_1.done; data_1_1 = data_1.next()) {
|
||||
var byte = data_1_1.value;
|
||||
this.checksum =
|
||||
(this.checksum >>> 8) ^ lookupTable[(this.checksum ^ byte) & 0xff];
|
||||
}
|
||||
}
|
||||
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (data_1_1 && !data_1_1.done && (_a = data_1.return)) _a.call(data_1);
|
||||
}
|
||||
finally { if (e_1) throw e_1.error; }
|
||||
}
|
||||
return this;
|
||||
};
|
||||
Crc32.prototype.digest = function () {
|
||||
return (this.checksum ^ 0xffffffff) >>> 0;
|
||||
};
|
||||
return Crc32;
|
||||
}());
|
||||
export { Crc32 };
|
||||
// prettier-ignore
|
||||
var a_lookUpTable = [
|
||||
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
|
||||
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
|
||||
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
|
||||
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
|
||||
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
|
||||
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
|
||||
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
|
||||
0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
|
||||
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
|
||||
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
|
||||
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
|
||||
0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
|
||||
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
|
||||
0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
|
||||
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
|
||||
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
|
||||
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
|
||||
0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
|
||||
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
|
||||
0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
|
||||
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
|
||||
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
|
||||
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
|
||||
0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
|
||||
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
|
||||
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
|
||||
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
|
||||
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
|
||||
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
|
||||
0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
|
||||
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
|
||||
0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
|
||||
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
|
||||
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
|
||||
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
|
||||
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
|
||||
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
|
||||
0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
|
||||
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
|
||||
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
|
||||
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
|
||||
0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
|
||||
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
|
||||
0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
|
||||
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
|
||||
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
|
||||
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
|
||||
0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
|
||||
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
|
||||
0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
|
||||
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
|
||||
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
|
||||
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
|
||||
0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
|
||||
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
|
||||
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
|
||||
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
|
||||
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
|
||||
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
|
||||
0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
|
||||
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
|
||||
0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
|
||||
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
|
||||
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D,
|
||||
];
|
||||
var lookupTable = uint32ArrayFrom(a_lookUpTable);
|
||||
export { AwsCrc32 } from "./aws_crc32";
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/module/index.js.map
generated
vendored
Normal file
1
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/build/module/index.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA,OAAO,EAAC,eAAe,EAAC,MAAM,kBAAkB,CAAC;AAEjD,MAAM,UAAU,KAAK,CAAC,IAAgB;IACpC,OAAO,IAAI,KAAK,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;AAC3C,CAAC;AAED;IAAA;QACU,aAAQ,GAAG,UAAU,CAAC;IAchC,CAAC;IAZC,sBAAM,GAAN,UAAO,IAAgB;;;YACrB,KAAmB,IAAA,SAAA,SAAA,IAAI,CAAA,0BAAA,4CAAE;gBAApB,IAAM,IAAI,iBAAA;gBACb,IAAI,CAAC,QAAQ;oBACX,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;aACtE;;;;;;;;;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED,sBAAM,GAAN;QACE,OAAO,CAAC,IAAI,CAAC,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC;IACH,YAAC;AAAD,CAAC,AAfD,IAeC;;AAED,kBAAkB;AAClB,IAAM,aAAa,GAAG;IACpB,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9C,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;CAC/C,CAAC;AACF,IAAM,WAAW,GAAgB,eAAe,CAAC,aAAa,CAAC,CAAA;AAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC"}
|
||||
32
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/package.json
generated
vendored
Normal file
32
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/package.json
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "@aws-crypto/crc32",
|
||||
"version": "5.2.0",
|
||||
"scripts": {
|
||||
"prepublishOnly": "tsc -p tsconfig.json && tsc -p tsconfig.module.json",
|
||||
"pretest": "tsc -p tsconfig.test.json",
|
||||
"test": "mocha --require ts-node/register test/**/*test.ts"
|
||||
},
|
||||
"main": "./build/main/index.js",
|
||||
"module": "./build/module/index.js",
|
||||
"types": "./build/main/index.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:aws/aws-sdk-js-crypto-helpers.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "AWS Crypto Tools Team",
|
||||
"email": "aws-cryptools@amazon.com",
|
||||
"url": "https://docs.aws.amazon.com/aws-crypto-tools/index.html?id=docs_gateway#lang/en_us"
|
||||
},
|
||||
"homepage": "https://github.com/aws/aws-sdk-js-crypto-helpers/tree/master/packages/crc32",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/util": "^5.2.0",
|
||||
"@aws-sdk/types": "^3.222.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"gitHead": "c11b171b35ec5c093364f0e0d8dc4ab1af68e748"
|
||||
}
|
||||
24
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/src/aws_crc32.ts
generated
vendored
Normal file
24
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/src/aws_crc32.ts
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { SourceData, Checksum } from "@aws-sdk/types";
|
||||
import { convertToBuffer, isEmptyData, numToUint8 } from "@aws-crypto/util";
|
||||
import { Crc32 } from "./index";
|
||||
|
||||
export class AwsCrc32 implements Checksum {
|
||||
private crc32 = new Crc32();
|
||||
|
||||
update(toHash: SourceData) {
|
||||
if (isEmptyData(toHash)) return;
|
||||
|
||||
this.crc32.update(convertToBuffer(toHash));
|
||||
}
|
||||
|
||||
async digest(): Promise<Uint8Array> {
|
||||
return numToUint8(this.crc32.digest());
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.crc32 = new Crc32();
|
||||
}
|
||||
}
|
||||
92
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/src/index.ts
generated
vendored
Normal file
92
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
import {uint32ArrayFrom} from "@aws-crypto/util";
|
||||
|
||||
export function crc32(data: Uint8Array): number {
|
||||
return new Crc32().update(data).digest();
|
||||
}
|
||||
|
||||
export class Crc32 {
|
||||
private checksum = 0xffffffff;
|
||||
|
||||
update(data: Uint8Array): this {
|
||||
for (const byte of data) {
|
||||
this.checksum =
|
||||
(this.checksum >>> 8) ^ lookupTable[(this.checksum ^ byte) & 0xff];
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
digest(): number {
|
||||
return (this.checksum ^ 0xffffffff) >>> 0;
|
||||
}
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
const a_lookUpTable = [
|
||||
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
|
||||
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
|
||||
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
|
||||
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
|
||||
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
|
||||
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
|
||||
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
|
||||
0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
|
||||
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
|
||||
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
|
||||
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
|
||||
0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
|
||||
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
|
||||
0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
|
||||
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
|
||||
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
|
||||
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
|
||||
0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
|
||||
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
|
||||
0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
|
||||
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
|
||||
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
|
||||
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
|
||||
0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
|
||||
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
|
||||
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
|
||||
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
|
||||
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
|
||||
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
|
||||
0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
|
||||
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
|
||||
0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
|
||||
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
|
||||
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
|
||||
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
|
||||
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
|
||||
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
|
||||
0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
|
||||
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
|
||||
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
|
||||
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
|
||||
0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
|
||||
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
|
||||
0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
|
||||
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
|
||||
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
|
||||
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
|
||||
0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
|
||||
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
|
||||
0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
|
||||
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
|
||||
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
|
||||
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
|
||||
0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
|
||||
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
|
||||
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
|
||||
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
|
||||
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
|
||||
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
|
||||
0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
|
||||
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
|
||||
0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
|
||||
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
|
||||
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D,
|
||||
];
|
||||
const lookupTable: Uint32Array = uint32ArrayFrom(a_lookUpTable)
|
||||
export { AwsCrc32 } from "./aws_crc32";
|
||||
9
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/tsconfig.json
generated
vendored
Normal file
9
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/tsconfig.json
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./build/main",
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules/**"]
|
||||
}
|
||||
7
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/tsconfig.module.json
generated
vendored
Normal file
7
crm_extensions/file_storage/node_modules/@aws-crypto/crc32/tsconfig.module.json
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "build/module",
|
||||
"module": "esnext",
|
||||
}
|
||||
}
|
||||
76
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/CHANGELOG.md
generated
vendored
Normal file
76
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [5.2.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v5.1.0...v5.2.0) (2023-10-16)
|
||||
|
||||
### Features
|
||||
|
||||
- support ESM artifacts in all packages ([#752](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/752)) ([e930ffb](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/e930ffba5cfef66dd242049e7d514ced232c1e3b))
|
||||
|
||||
# [5.1.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v5.0.0...v5.1.0) (2023-09-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Update tsc to 2.x ([#735](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/735)) ([782e0de](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/782e0de9f5fef41f694130580a69d940894b6b8c))
|
||||
|
||||
### Features
|
||||
|
||||
- Use @smithy/util-utf8 ([#730](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/730)) ([00fb851](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/00fb851ca3559d5a1f370f9256814de1210826b8)), closes [#699](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/699)
|
||||
|
||||
# [5.0.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v4.0.1...v5.0.0) (2023-07-13)
|
||||
|
||||
**Note:** Version bump only for package @aws-crypto/crc32c
|
||||
|
||||
# [4.0.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v3.0.0...v4.0.0) (2023-02-20)
|
||||
|
||||
**Note:** Version bump only for package @aws-crypto/crc32c
|
||||
|
||||
# [3.0.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v2.0.2...v3.0.0) (2023-01-12)
|
||||
|
||||
- feat!: replace Hash implementations with Checksum interface (#492) ([da43dc0](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/da43dc0fdf669d9ebb5bfb1b1f7c79e46c4aaae1)), closes [#492](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/492)
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
- All classes that implemented `Hash` now implement `Checksum`.
|
||||
|
||||
## [2.0.2](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v2.0.1...v2.0.2) (2022-09-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **#337:** update @aws-sdk/types ([#373](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/373)) ([b26a811](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/b26a811a392f5209c7ec7e57251500d4d78f97ff)), closes [#337](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/337)
|
||||
|
||||
## [2.0.1](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v2.0.0...v2.0.1) (2021-12-09)
|
||||
|
||||
**Note:** Version bump only for package @aws-crypto/crc32c
|
||||
|
||||
# [2.0.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v1.2.2...v2.0.0) (2021-10-25)
|
||||
|
||||
**Note:** Version bump only for package @aws-crypto/crc32c
|
||||
|
||||
## [1.2.2](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v1.2.1...v1.2.2) (2021-10-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **crc32c:** ie11 does not support Array.from ([#221](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/221)) ([5f49547](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/5f495472ab8988cf203e0f2a70a51f7e1fcd7e60))
|
||||
|
||||
## [1.2.1](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v1.2.0...v1.2.1) (2021-09-17)
|
||||
|
||||
**Note:** Version bump only for package @aws-crypto/crc32c
|
||||
|
||||
# [1.2.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v1.1.1...v1.2.0) (2021-09-17)
|
||||
|
||||
### Features
|
||||
|
||||
- Add AwsCrc32C Hash ([4840c83](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/4840c83bdd7c461dded777ebc45a8f99258ba21c))
|
||||
|
||||
## [0.2.1](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/@aws-crypto/crc32c@0.2.0...@aws-crypto/crc32c@0.2.1) (2021-08-24)
|
||||
|
||||
**Note:** Version bump only for package @aws-crypto/crc32c
|
||||
|
||||
# 0.2.0 (2021-08-11)
|
||||
|
||||
### Features
|
||||
|
||||
- Create CRC-32C implementation ([#201](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/201)) ([e43c7ec](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/e43c7ecd30d6499fa696f5839ecc30502a34b8b6))
|
||||
201
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/LICENSE
generated
vendored
Normal file
201
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
16
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/README.md
generated
vendored
Normal file
16
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/README.md
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# @aws-crypto/crc32c
|
||||
|
||||
Pure JS implementation of CRC32-C https://en.wikipedia.org/wiki/Cyclic_redundancy_check
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
import { Crc32c } from '@aws-crypto/crc32c';
|
||||
|
||||
const crc32Digest = (new Crc32c).update(buffer).digest()
|
||||
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
`npm test`
|
||||
7
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/main/aws_crc32c.d.ts
generated
vendored
Normal file
7
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/main/aws_crc32c.d.ts
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Checksum, SourceData } from "@aws-sdk/types";
|
||||
export declare class AwsCrc32c implements Checksum {
|
||||
private crc32c;
|
||||
update(toHash: SourceData): void;
|
||||
digest(): Promise<Uint8Array>;
|
||||
reset(): void;
|
||||
}
|
||||
31
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/main/aws_crc32c.js
generated
vendored
Normal file
31
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/main/aws_crc32c.js
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
"use strict";
|
||||
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AwsCrc32c = void 0;
|
||||
var tslib_1 = require("tslib");
|
||||
var util_1 = require("@aws-crypto/util");
|
||||
var index_1 = require("./index");
|
||||
var AwsCrc32c = /** @class */ (function () {
|
||||
function AwsCrc32c() {
|
||||
this.crc32c = new index_1.Crc32c();
|
||||
}
|
||||
AwsCrc32c.prototype.update = function (toHash) {
|
||||
if ((0, util_1.isEmptyData)(toHash))
|
||||
return;
|
||||
this.crc32c.update((0, util_1.convertToBuffer)(toHash));
|
||||
};
|
||||
AwsCrc32c.prototype.digest = function () {
|
||||
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
||||
return tslib_1.__generator(this, function (_a) {
|
||||
return [2 /*return*/, (0, util_1.numToUint8)(this.crc32c.digest())];
|
||||
});
|
||||
});
|
||||
};
|
||||
AwsCrc32c.prototype.reset = function () {
|
||||
this.crc32c = new index_1.Crc32c();
|
||||
};
|
||||
return AwsCrc32c;
|
||||
}());
|
||||
exports.AwsCrc32c = AwsCrc32c;
|
||||
//# sourceMappingURL=aws_crc32c.js.map
|
||||
1
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/main/aws_crc32c.js.map
generated
vendored
Normal file
1
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/main/aws_crc32c.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"aws_crc32c.js","sourceRoot":"","sources":["../../src/aws_crc32c.ts"],"names":[],"mappings":";AAAA,oEAAoE;AACpE,sCAAsC;;;;AAGtC,yCAA4E;AAC5E,iCAAiC;AAEjC;IAAA;QACU,WAAM,GAAG,IAAI,cAAM,EAAE,CAAC;IAehC,CAAC;IAbC,0BAAM,GAAN,UAAO,MAAkB;QACvB,IAAI,IAAA,kBAAW,EAAC,MAAM,CAAC;YAAE,OAAO;QAEhC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAA,sBAAe,EAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,CAAC;IAEK,0BAAM,GAAZ;;;gBACE,sBAAO,IAAA,iBAAU,EAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,EAAC;;;KACzC;IAED,yBAAK,GAAL;QACE,IAAI,CAAC,MAAM,GAAG,IAAI,cAAM,EAAE,CAAC;IAC7B,CAAC;IACH,gBAAC;AAAD,CAAC,AAhBD,IAgBC;AAhBY,8BAAS"}
|
||||
7
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/main/index.d.ts
generated
vendored
Normal file
7
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/main/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export declare function crc32c(data: Uint8Array): number;
|
||||
export declare class Crc32c {
|
||||
private checksum;
|
||||
update(data: Uint8Array): this;
|
||||
digest(): number;
|
||||
}
|
||||
export { AwsCrc32c } from "./aws_crc32c";
|
||||
78
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/main/index.js
generated
vendored
Normal file
78
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/main/index.js
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
"use strict";
|
||||
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AwsCrc32c = exports.Crc32c = exports.crc32c = void 0;
|
||||
var tslib_1 = require("tslib");
|
||||
var util_1 = require("@aws-crypto/util");
|
||||
function crc32c(data) {
|
||||
return new Crc32c().update(data).digest();
|
||||
}
|
||||
exports.crc32c = crc32c;
|
||||
var Crc32c = /** @class */ (function () {
|
||||
function Crc32c() {
|
||||
this.checksum = 0xffffffff;
|
||||
}
|
||||
Crc32c.prototype.update = function (data) {
|
||||
var e_1, _a;
|
||||
try {
|
||||
for (var data_1 = tslib_1.__values(data), data_1_1 = data_1.next(); !data_1_1.done; data_1_1 = data_1.next()) {
|
||||
var byte = data_1_1.value;
|
||||
this.checksum =
|
||||
(this.checksum >>> 8) ^ lookupTable[(this.checksum ^ byte) & 0xff];
|
||||
}
|
||||
}
|
||||
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (data_1_1 && !data_1_1.done && (_a = data_1.return)) _a.call(data_1);
|
||||
}
|
||||
finally { if (e_1) throw e_1.error; }
|
||||
}
|
||||
return this;
|
||||
};
|
||||
Crc32c.prototype.digest = function () {
|
||||
return (this.checksum ^ 0xffffffff) >>> 0;
|
||||
};
|
||||
return Crc32c;
|
||||
}());
|
||||
exports.Crc32c = Crc32c;
|
||||
// prettier-ignore
|
||||
var a_lookupTable = [
|
||||
0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB,
|
||||
0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24,
|
||||
0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384,
|
||||
0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B,
|
||||
0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35,
|
||||
0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA,
|
||||
0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A,
|
||||
0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595,
|
||||
0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957,
|
||||
0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198,
|
||||
0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38,
|
||||
0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7,
|
||||
0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789,
|
||||
0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46,
|
||||
0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6,
|
||||
0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829,
|
||||
0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93,
|
||||
0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C,
|
||||
0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC,
|
||||
0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033,
|
||||
0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D,
|
||||
0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982,
|
||||
0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622,
|
||||
0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED,
|
||||
0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F,
|
||||
0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0,
|
||||
0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540,
|
||||
0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F,
|
||||
0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1,
|
||||
0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E,
|
||||
0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E,
|
||||
0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351,
|
||||
];
|
||||
var lookupTable = (0, util_1.uint32ArrayFrom)(a_lookupTable);
|
||||
var aws_crc32c_1 = require("./aws_crc32c");
|
||||
Object.defineProperty(exports, "AwsCrc32c", { enumerable: true, get: function () { return aws_crc32c_1.AwsCrc32c; } });
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/main/index.js.map
generated
vendored
Normal file
1
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/main/index.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA,oEAAoE;AACpE,sCAAsC;;;;AAEtC,yCAAiD;AAEjD,SAAgB,MAAM,CAAC,IAAgB;IACrC,OAAO,IAAI,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;AAC5C,CAAC;AAFD,wBAEC;AAED;IAAA;QACU,aAAQ,GAAG,UAAU,CAAC;IAchC,CAAC;IAZC,uBAAM,GAAN,UAAO,IAAgB;;;YACrB,KAAmB,IAAA,SAAA,iBAAA,IAAI,CAAA,0BAAA,4CAAE;gBAApB,IAAM,IAAI,iBAAA;gBACb,IAAI,CAAC,QAAQ;oBACX,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;aACtE;;;;;;;;;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED,uBAAM,GAAN;QACE,OAAO,CAAC,IAAI,CAAC,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC;IACH,aAAC;AAAD,CAAC,AAfD,IAeC;AAfY,wBAAM;AAiBnB,kBAAkB;AAClB,IAAM,aAAa,GAAG;IACpB,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;CAC/F,CAAC;AAEF,IAAM,WAAW,GAAgB,IAAA,sBAAe,EAAC,aAAa,CAAC,CAAA;AAC/D,2CAAyC;AAAhC,uGAAA,SAAS,OAAA"}
|
||||
7
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/module/aws_crc32c.d.ts
generated
vendored
Normal file
7
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/module/aws_crc32c.d.ts
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Checksum, SourceData } from "@aws-sdk/types";
|
||||
export declare class AwsCrc32c implements Checksum {
|
||||
private crc32c;
|
||||
update(toHash: SourceData): void;
|
||||
digest(): Promise<Uint8Array>;
|
||||
reset(): void;
|
||||
}
|
||||
28
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/module/aws_crc32c.js
generated
vendored
Normal file
28
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/module/aws_crc32c.js
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import { __awaiter, __generator } from "tslib";
|
||||
import { convertToBuffer, isEmptyData, numToUint8 } from "@aws-crypto/util";
|
||||
import { Crc32c } from "./index";
|
||||
var AwsCrc32c = /** @class */ (function () {
|
||||
function AwsCrc32c() {
|
||||
this.crc32c = new Crc32c();
|
||||
}
|
||||
AwsCrc32c.prototype.update = function (toHash) {
|
||||
if (isEmptyData(toHash))
|
||||
return;
|
||||
this.crc32c.update(convertToBuffer(toHash));
|
||||
};
|
||||
AwsCrc32c.prototype.digest = function () {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
return __generator(this, function (_a) {
|
||||
return [2 /*return*/, numToUint8(this.crc32c.digest())];
|
||||
});
|
||||
});
|
||||
};
|
||||
AwsCrc32c.prototype.reset = function () {
|
||||
this.crc32c = new Crc32c();
|
||||
};
|
||||
return AwsCrc32c;
|
||||
}());
|
||||
export { AwsCrc32c };
|
||||
//# sourceMappingURL=aws_crc32c.js.map
|
||||
1
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/module/aws_crc32c.js.map
generated
vendored
Normal file
1
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/module/aws_crc32c.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"aws_crc32c.js","sourceRoot":"","sources":["../../src/aws_crc32c.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,sCAAsC;;AAGtC,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC;IAAA;QACU,WAAM,GAAG,IAAI,MAAM,EAAE,CAAC;IAehC,CAAC;IAbC,0BAAM,GAAN,UAAO,MAAkB;QACvB,IAAI,WAAW,CAAC,MAAM,CAAC;YAAE,OAAO;QAEhC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,CAAC;IAEK,0BAAM,GAAZ;;;gBACE,sBAAO,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,EAAC;;;KACzC;IAED,yBAAK,GAAL;QACE,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;IAC7B,CAAC;IACH,gBAAC;AAAD,CAAC,AAhBD,IAgBC"}
|
||||
7
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/module/index.d.ts
generated
vendored
Normal file
7
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/module/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export declare function crc32c(data: Uint8Array): number;
|
||||
export declare class Crc32c {
|
||||
private checksum;
|
||||
update(data: Uint8Array): this;
|
||||
digest(): number;
|
||||
}
|
||||
export { AwsCrc32c } from "./aws_crc32c";
|
||||
73
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/module/index.js
generated
vendored
Normal file
73
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/module/index.js
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import { __values } from "tslib";
|
||||
import { uint32ArrayFrom } from "@aws-crypto/util";
|
||||
export function crc32c(data) {
|
||||
return new Crc32c().update(data).digest();
|
||||
}
|
||||
var Crc32c = /** @class */ (function () {
|
||||
function Crc32c() {
|
||||
this.checksum = 0xffffffff;
|
||||
}
|
||||
Crc32c.prototype.update = function (data) {
|
||||
var e_1, _a;
|
||||
try {
|
||||
for (var data_1 = __values(data), data_1_1 = data_1.next(); !data_1_1.done; data_1_1 = data_1.next()) {
|
||||
var byte = data_1_1.value;
|
||||
this.checksum =
|
||||
(this.checksum >>> 8) ^ lookupTable[(this.checksum ^ byte) & 0xff];
|
||||
}
|
||||
}
|
||||
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (data_1_1 && !data_1_1.done && (_a = data_1.return)) _a.call(data_1);
|
||||
}
|
||||
finally { if (e_1) throw e_1.error; }
|
||||
}
|
||||
return this;
|
||||
};
|
||||
Crc32c.prototype.digest = function () {
|
||||
return (this.checksum ^ 0xffffffff) >>> 0;
|
||||
};
|
||||
return Crc32c;
|
||||
}());
|
||||
export { Crc32c };
|
||||
// prettier-ignore
|
||||
var a_lookupTable = [
|
||||
0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB,
|
||||
0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24,
|
||||
0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384,
|
||||
0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B,
|
||||
0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35,
|
||||
0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA,
|
||||
0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A,
|
||||
0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595,
|
||||
0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957,
|
||||
0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198,
|
||||
0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38,
|
||||
0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7,
|
||||
0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789,
|
||||
0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46,
|
||||
0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6,
|
||||
0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829,
|
||||
0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93,
|
||||
0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C,
|
||||
0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC,
|
||||
0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033,
|
||||
0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D,
|
||||
0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982,
|
||||
0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622,
|
||||
0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED,
|
||||
0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F,
|
||||
0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0,
|
||||
0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540,
|
||||
0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F,
|
||||
0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1,
|
||||
0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E,
|
||||
0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E,
|
||||
0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351,
|
||||
];
|
||||
var lookupTable = uint32ArrayFrom(a_lookupTable);
|
||||
export { AwsCrc32c } from "./aws_crc32c";
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/module/index.js.map
generated
vendored
Normal file
1
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/build/module/index.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,sCAAsC;;AAEtC,OAAO,EAAC,eAAe,EAAC,MAAM,kBAAkB,CAAC;AAEjD,MAAM,UAAU,MAAM,CAAC,IAAgB;IACrC,OAAO,IAAI,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;AAC5C,CAAC;AAED;IAAA;QACU,aAAQ,GAAG,UAAU,CAAC;IAchC,CAAC;IAZC,uBAAM,GAAN,UAAO,IAAgB;;;YACrB,KAAmB,IAAA,SAAA,SAAA,IAAI,CAAA,0BAAA,4CAAE;gBAApB,IAAM,IAAI,iBAAA;gBACb,IAAI,CAAC,QAAQ;oBACX,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;aACtE;;;;;;;;;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED,uBAAM,GAAN;QACE,OAAO,CAAC,IAAI,CAAC,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC;IACH,aAAC;AAAD,CAAC,AAfD,IAeC;;AAED,kBAAkB;AAClB,IAAM,aAAa,GAAG;IACpB,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;IAC9F,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;CAC/F,CAAC;AAEF,IAAM,WAAW,GAAgB,eAAe,CAAC,aAAa,CAAC,CAAA;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC"}
|
||||
32
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/package.json
generated
vendored
Normal file
32
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/package.json
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "@aws-crypto/crc32c",
|
||||
"version": "5.2.0",
|
||||
"scripts": {
|
||||
"prepublishOnly": "tsc -p tsconfig.json && tsc -p tsconfig.module.json",
|
||||
"pretest": "tsc -p tsconfig.test.json",
|
||||
"test": "mocha --require ts-node/register test/**/*test.ts"
|
||||
},
|
||||
"main": "./build/main/index.js",
|
||||
"module": "./build/module/index.js",
|
||||
"types": "./build/main/index.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:aws/aws-sdk-js-crypto-helpers.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "AWS Crypto Tools Team",
|
||||
"email": "aws-cryptools@amazon.com",
|
||||
"url": "https://docs.aws.amazon.com/aws-crypto-tools/index.html?id=docs_gateway#lang/en_us"
|
||||
},
|
||||
"homepage": "https://github.com/aws/aws-sdk-js-crypto-helpers/tree/master/packages/crc32c",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/util": "^5.2.0",
|
||||
"@aws-sdk/types": "^3.222.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "c11b171b35ec5c093364f0e0d8dc4ab1af68e748"
|
||||
}
|
||||
24
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/src/aws_crc32c.ts
generated
vendored
Normal file
24
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/src/aws_crc32c.ts
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { Checksum, SourceData } from "@aws-sdk/types";
|
||||
import { convertToBuffer, isEmptyData, numToUint8 } from "@aws-crypto/util";
|
||||
import { Crc32c } from "./index";
|
||||
|
||||
export class AwsCrc32c implements Checksum {
|
||||
private crc32c = new Crc32c();
|
||||
|
||||
update(toHash: SourceData) {
|
||||
if (isEmptyData(toHash)) return;
|
||||
|
||||
this.crc32c.update(convertToBuffer(toHash));
|
||||
}
|
||||
|
||||
async digest(): Promise<Uint8Array> {
|
||||
return numToUint8(this.crc32c.digest());
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.crc32c = new Crc32c();
|
||||
}
|
||||
}
|
||||
64
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/src/index.ts
generated
vendored
Normal file
64
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import {uint32ArrayFrom} from "@aws-crypto/util";
|
||||
|
||||
export function crc32c(data: Uint8Array): number {
|
||||
return new Crc32c().update(data).digest();
|
||||
}
|
||||
|
||||
export class Crc32c {
|
||||
private checksum = 0xffffffff;
|
||||
|
||||
update(data: Uint8Array): this {
|
||||
for (const byte of data) {
|
||||
this.checksum =
|
||||
(this.checksum >>> 8) ^ lookupTable[(this.checksum ^ byte) & 0xff];
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
digest(): number {
|
||||
return (this.checksum ^ 0xffffffff) >>> 0;
|
||||
}
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
const a_lookupTable = [
|
||||
0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB,
|
||||
0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24,
|
||||
0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384,
|
||||
0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B,
|
||||
0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35,
|
||||
0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA,
|
||||
0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A,
|
||||
0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595,
|
||||
0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957,
|
||||
0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198,
|
||||
0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38,
|
||||
0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7,
|
||||
0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789,
|
||||
0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46,
|
||||
0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6,
|
||||
0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829,
|
||||
0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93,
|
||||
0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C,
|
||||
0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC,
|
||||
0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033,
|
||||
0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D,
|
||||
0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982,
|
||||
0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622,
|
||||
0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED,
|
||||
0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F,
|
||||
0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0,
|
||||
0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540,
|
||||
0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F,
|
||||
0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1,
|
||||
0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E,
|
||||
0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E,
|
||||
0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351,
|
||||
];
|
||||
|
||||
const lookupTable: Uint32Array = uint32ArrayFrom(a_lookupTable)
|
||||
export { AwsCrc32c } from "./aws_crc32c";
|
||||
9
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/tsconfig.json
generated
vendored
Normal file
9
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/tsconfig.json
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./build/main",
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules/**"]
|
||||
}
|
||||
7
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/tsconfig.module.json
generated
vendored
Normal file
7
crm_extensions/file_storage/node_modules/@aws-crypto/crc32c/tsconfig.module.json
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "build/module",
|
||||
"module": "esnext",
|
||||
}
|
||||
}
|
||||
62
crm_extensions/file_storage/node_modules/@aws-crypto/sha1-browser/CHANGELOG.md
generated
vendored
Normal file
62
crm_extensions/file_storage/node_modules/@aws-crypto/sha1-browser/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [5.2.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v5.1.0...v5.2.0) (2023-10-16)
|
||||
|
||||
### Features
|
||||
|
||||
- support ESM artifacts in all packages ([#752](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/752)) ([e930ffb](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/e930ffba5cfef66dd242049e7d514ced232c1e3b))
|
||||
|
||||
# [5.1.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v5.0.0...v5.1.0) (2023-09-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Update tsc to 2.x ([#735](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/735)) ([782e0de](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/782e0de9f5fef41f694130580a69d940894b6b8c))
|
||||
|
||||
### Features
|
||||
|
||||
- Use @smithy/util-utf8 ([#730](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/730)) ([00fb851](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/00fb851ca3559d5a1f370f9256814de1210826b8)), closes [#699](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/699)
|
||||
|
||||
# [5.0.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v4.0.1...v5.0.0) (2023-07-13)
|
||||
|
||||
- feat!: drop support for IE 11 (#629) ([6c49fb6](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/6c49fb6c1b1f18bbff02dbd77a37a21bdb40c959)), closes [#629](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/629)
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
- Remove support for IE11
|
||||
|
||||
Co-authored-by: texastony <5892063+texastony@users.noreply.github.com>
|
||||
|
||||
# [4.0.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v3.0.0...v4.0.0) (2023-02-20)
|
||||
|
||||
**Note:** Version bump only for package @aws-crypto/sha1-browser
|
||||
|
||||
# [3.0.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v2.0.2...v3.0.0) (2023-01-12)
|
||||
|
||||
- feat!: replace Hash implementations with Checksum interface (#492) ([da43dc0](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/da43dc0fdf669d9ebb5bfb1b1f7c79e46c4aaae1)), closes [#492](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/492)
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
- All classes that implemented `Hash` now implement `Checksum`.
|
||||
|
||||
## [2.0.2](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v2.0.1...v2.0.2) (2022-09-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **#337:** update @aws-sdk/types ([#373](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/373)) ([b26a811](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/b26a811a392f5209c7ec7e57251500d4d78f97ff)), closes [#337](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/337)
|
||||
|
||||
# [2.0.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v1.2.2...v2.0.0) (2021-10-25)
|
||||
|
||||
**Note:** Version bump only for package @aws-crypto/sha1-browser
|
||||
|
||||
# [1.2.0](https://github.com/aws/aws-sdk-js-crypto-helpers/compare/v1.1.1...v1.2.0) (2021-09-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Adding ie11-detection dependency to sha1-browser ([#213](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/213)) ([138750d](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/138750d96385b8cc479b6f54c500ee1b5380648c))
|
||||
|
||||
### Features
|
||||
|
||||
- Add SHA1 ([#208](https://github.com/aws/aws-sdk-js-crypto-helpers/issues/208)) ([45c50ff](https://github.com/aws/aws-sdk-js-crypto-helpers/commit/45c50ffa3acc9e3bf4039ab59a0102e4d40455ec))
|
||||
202
crm_extensions/file_storage/node_modules/@aws-crypto/sha1-browser/LICENSE
generated
vendored
Normal file
202
crm_extensions/file_storage/node_modules/@aws-crypto/sha1-browser/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
21
crm_extensions/file_storage/node_modules/@aws-crypto/sha1-browser/README.md
generated
vendored
Normal file
21
crm_extensions/file_storage/node_modules/@aws-crypto/sha1-browser/README.md
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# @aws-crypto/sha1-browser
|
||||
|
||||
SHA1 wrapper for browsers that prefers `window.crypto.subtle`.
|
||||
|
||||
SHA1 is **NOT** a cryptographically secure algorithm.
|
||||
It should _only_ be used for non cryptographic functions like checksums.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
import {Sha1} from '@aws-crypto/sha1-browser'
|
||||
|
||||
const hash = new Sha1();
|
||||
hash.update('some data');
|
||||
const result = await hash.digest();
|
||||
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
`npm test`
|
||||
10
crm_extensions/file_storage/node_modules/@aws-crypto/sha1-browser/build/main/constants.d.ts
generated
vendored
Normal file
10
crm_extensions/file_storage/node_modules/@aws-crypto/sha1-browser/build/main/constants.d.ts
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
export declare const SHA_1_HASH: {
|
||||
name: "SHA-1";
|
||||
};
|
||||
export declare const SHA_1_HMAC_ALGO: {
|
||||
name: "HMAC";
|
||||
hash: {
|
||||
name: "SHA-1";
|
||||
};
|
||||
};
|
||||
export declare const EMPTY_DATA_SHA_1: Uint8Array;
|
||||
31
crm_extensions/file_storage/node_modules/@aws-crypto/sha1-browser/build/main/constants.js
generated
vendored
Normal file
31
crm_extensions/file_storage/node_modules/@aws-crypto/sha1-browser/build/main/constants.js
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.EMPTY_DATA_SHA_1 = exports.SHA_1_HMAC_ALGO = exports.SHA_1_HASH = void 0;
|
||||
exports.SHA_1_HASH = { name: "SHA-1" };
|
||||
exports.SHA_1_HMAC_ALGO = {
|
||||
name: "HMAC",
|
||||
hash: exports.SHA_1_HASH,
|
||||
};
|
||||
exports.EMPTY_DATA_SHA_1 = new Uint8Array([
|
||||
218,
|
||||
57,
|
||||
163,
|
||||
238,
|
||||
94,
|
||||
107,
|
||||
75,
|
||||
13,
|
||||
50,
|
||||
85,
|
||||
191,
|
||||
239,
|
||||
149,
|
||||
96,
|
||||
24,
|
||||
144,
|
||||
175,
|
||||
216,
|
||||
7,
|
||||
9,
|
||||
]);
|
||||
//# sourceMappingURL=constants.js.map
|
||||
1
crm_extensions/file_storage/node_modules/@aws-crypto/sha1-browser/build/main/constants.js.map
generated
vendored
Normal file
1
crm_extensions/file_storage/node_modules/@aws-crypto/sha1-browser/build/main/constants.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":";;;AAAa,QAAA,UAAU,GAAsB,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAElD,QAAA,eAAe,GAA8C;IACxE,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,kBAAU;CACjB,CAAC;AAEW,QAAA,gBAAgB,GAAG,IAAI,UAAU,CAAC;IAC7C,GAAG;IACH,EAAE;IACF,GAAG;IACH,GAAG;IACH,EAAE;IACF,GAAG;IACH,EAAE;IACF,EAAE;IACF,EAAE;IACF,EAAE;IACF,GAAG;IACH,GAAG;IACH,GAAG;IACH,EAAE;IACF,EAAE;IACF,GAAG;IACH,GAAG;IACH,GAAG;IACH,CAAC;IACD,CAAC;CACF,CAAC,CAAC"}
|
||||
8
crm_extensions/file_storage/node_modules/@aws-crypto/sha1-browser/build/main/crossPlatformSha1.d.ts
generated
vendored
Normal file
8
crm_extensions/file_storage/node_modules/@aws-crypto/sha1-browser/build/main/crossPlatformSha1.d.ts
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Checksum, SourceData } from "@aws-sdk/types";
|
||||
export declare class Sha1 implements Checksum {
|
||||
private hash;
|
||||
constructor(secret?: SourceData);
|
||||
update(data: SourceData, encoding?: "utf8" | "ascii" | "latin1"): void;
|
||||
digest(): Promise<Uint8Array>;
|
||||
reset(): void;
|
||||
}
|
||||
29
crm_extensions/file_storage/node_modules/@aws-crypto/sha1-browser/build/main/crossPlatformSha1.js
generated
vendored
Normal file
29
crm_extensions/file_storage/node_modules/@aws-crypto/sha1-browser/build/main/crossPlatformSha1.js
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Sha1 = void 0;
|
||||
var webCryptoSha1_1 = require("./webCryptoSha1");
|
||||
var supports_web_crypto_1 = require("@aws-crypto/supports-web-crypto");
|
||||
var util_locate_window_1 = require("@aws-sdk/util-locate-window");
|
||||
var util_1 = require("@aws-crypto/util");
|
||||
var Sha1 = /** @class */ (function () {
|
||||
function Sha1(secret) {
|
||||
if ((0, supports_web_crypto_1.supportsWebCrypto)((0, util_locate_window_1.locateWindow)())) {
|
||||
this.hash = new webCryptoSha1_1.Sha1(secret);
|
||||
}
|
||||
else {
|
||||
throw new Error("SHA1 not supported");
|
||||
}
|
||||
}
|
||||
Sha1.prototype.update = function (data, encoding) {
|
||||
this.hash.update((0, util_1.convertToBuffer)(data));
|
||||
};
|
||||
Sha1.prototype.digest = function () {
|
||||
return this.hash.digest();
|
||||
};
|
||||
Sha1.prototype.reset = function () {
|
||||
this.hash.reset();
|
||||
};
|
||||
return Sha1;
|
||||
}());
|
||||
exports.Sha1 = Sha1;
|
||||
//# sourceMappingURL=crossPlatformSha1.js.map
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user