diff --git a/.gitignore b/.gitignore index 6cc620860a..ce72c8e948 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ node_modules/ /test-results/ /blob-report/ /playwright/.cache/ +GOOGLE_DRIVE_DEPLOYMENT.md diff --git a/DEVELOPMENT_WORKFLOW.md b/DEVELOPMENT_WORKFLOW.md new file mode 100644 index 0000000000..15814033b4 --- /dev/null +++ b/DEVELOPMENT_WORKFLOW.md @@ -0,0 +1,417 @@ +# STYLEUS CRM - Development Workflow Guide + +## 🔄 Синхронизация локальной и production среды + +--- + +## Рекомендуемый подход: Git + GitHub/GitLab + +### Преимущества: +- ✅ Версионный контроль +- ✅ История изменений +- ✅ Откат к предыдущим версиям +- ✅ Командная работа +- ✅ Автоматический deployment + +--- + +## 📋 Вариант 1: Git Workflow (РЕКОМЕНДУЕТСЯ) + +### Шаг 1: Инициализация Git репозитория + +```bash +# На вашем Mac в директории проекта +cd "/Users/sergeirybakov/StyleUS-Tools/STYLEUS_CRM/Krayin CRM/styleuscrm" + +# Инициализируйте Git (если еще не сделано) +git init + +# Добавьте .gitignore +cat > .gitignore << 'EOF' +/node_modules +/public/hot +/public/storage +/storage/*.key +/vendor +.env +.env.backup +.phpunit.result.cache +Homestead.json +Homestead.yaml +npm-debug.log +yarn-error.log +/.idea +/.vscode +EOF + +# Добавьте все файлы +git add . +git commit -m "Initial commit - STYLEUS CRM" +``` + +### Шаг 2: Создайте GitHub/GitLab репозиторий + +**GitHub (рекомендую):** +1. Перейдите на https://github.com +2. Нажмите "New repository" +3. Название: `styleus-crm` +4. Выберите: Private (для безопасности) +5. Нажмите "Create repository" + +```bash +# Подключите удаленный репозиторий +git remote add origin https://github.com/YOUR_USERNAME/styleus-crm.git +git branch -M main +git push -u origin main +``` + +### Шаг 3: Настройте deployment на сервере + +**На сервере создайте deploy ключ:** + +```bash +# Подключитесь к серверу +ssh root@45.55.62.115 + +# Создайте SSH ключ для GitHub +ssh-keygen -t ed25519 -C "deploy@styleus.us" -f ~/.ssh/github_deploy +cat ~/.ssh/github_deploy.pub +# Скопируйте вывод +``` + +**Добавьте Deploy Key в GitHub:** +1. GitHub Repository → Settings → Deploy keys +2. Add deploy key +3. Title: "Production Server" +4. Key: вставьте скопированный ключ +5. ✅ Allow write access (если нужно) +6. Add key + +**Настройте Git на сервере:** + +```bash +# На сервере +cd /var/www/styleus + +# Инициализируйте Git +git init +git remote add origin git@github.com:YOUR_USERNAME/styleus-crm.git + +# Настройте SSH +cat > ~/.ssh/config << 'EOF' +Host github.com + HostName github.com + User git + IdentityFile ~/.ssh/github_deploy +EOF + +chmod 600 ~/.ssh/config + +# Первый pull +git fetch origin main +git reset --hard origin/main +``` + +### Шаг 4: Workflow для разработки + +#### Локальная разработка: + +```bash +# 1. Внесите изменения в код +# 2. Протестируйте локально +php artisan serve + +# 3. Закоммитьте изменения +git add . +git commit -m "Описание изменений" + +# 4. Отправьте на GitHub +git push origin main +``` + +#### Обновление production: + +```bash +# Подключитесь к серверу +ssh root@45.55.62.115 + +# Перейдите в директорию +cd /var/www/styleus + +# Включите maintenance mode +php artisan down + +# Получите последние изменения +git pull origin main + +# Обновите зависимости (если нужно) +composer install --no-dev --optimize-autoloader +npm ci --production +npm run build + +# Выполните миграции (если есть новые) +php artisan migrate --force + +# Очистите кеш +php artisan config:cache +php artisan route:cache +php artisan view:cache + +# Перезапустите очереди +sudo supervisorctl restart styleuscrm-worker:* + +# Выключите maintenance mode +php artisan up +``` + +--- + +## 📋 Вариант 2: Автоматический Deployment (GitHub Actions) + +Создайте файл `.github/workflows/deploy.yml`: + +```yaml +name: Deploy to Production + +on: + push: + branches: [ main ] + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Deploy to server + uses: appleboy/ssh-action@master + with: + host: 45.55.62.115 + username: root + key: ${{ secrets.SSH_PRIVATE_KEY }} + script: | + cd /var/www/styleus + php artisan down + git pull origin main + composer install --no-dev --optimize-autoloader + npm ci --production + npm run build + php artisan migrate --force + php artisan config:cache + php artisan route:cache + php artisan view:cache + sudo supervisorctl restart styleuscrm-worker:* + php artisan up +``` + +**Настройка GitHub Secrets:** +1. Repository → Settings → Secrets and variables → Actions +2. New repository secret +3. Name: `SSH_PRIVATE_KEY` +4. Value: содержимое вашего приватного SSH ключа + +**Теперь при каждом `git push` код автоматически развернется на сервере!** + +--- + +## 📋 Вариант 3: Rsync (Быстрая синхронизация) + +Для быстрой синхронизации без Git: + +```bash +# Создайте скрипт sync.sh на Mac +cat > sync-to-production.sh << 'EOF' +#!/bin/bash + +echo "🔄 Syncing to production server..." + +rsync -avz --exclude 'node_modules' \ + --exclude 'vendor' \ + --exclude '.git' \ + --exclude 'storage/logs/*' \ + --exclude 'storage/framework/cache/*' \ + --exclude 'storage/framework/sessions/*' \ + --exclude 'storage/framework/views/*' \ + --exclude '.env' \ + . root@45.55.62.115:/var/www/styleus/ + +echo "✅ Sync complete!" +echo "🔄 Clearing cache on server..." + +ssh root@45.55.62.115 << 'ENDSSH' +cd /var/www/styleus +php artisan config:cache +php artisan route:cache +php artisan view:cache +sudo supervisorctl restart styleuscrm-worker:* +ENDSSH + +echo "✅ Done!" +EOF + +chmod +x sync-to-production.sh +``` + +**Использование:** + +```bash +./sync-to-production.sh +``` + +--- + +## 📋 Вариант 4: Laravel Forge (Платный, но простой) + +**Laravel Forge** (https://forge.laravel.com) - $12/месяц + +### Преимущества: +- ✅ Автоматический deployment при push в Git +- ✅ Управление через веб-интерфейс +- ✅ SSL сертификаты в один клик +- ✅ Мониторинг и логи +- ✅ Scheduled jobs и queue workers +- ✅ Резервное копирование + +### Как использовать: +1. Зарегистрируйтесь на forge.laravel.com +2. Подключите ваш DigitalOcean аккаунт +3. Подключите GitHub репозиторий +4. Forge автоматически развернет и будет обновлять приложение + +--- + +## 🗄️ Синхронизация базы данных + +### Локальная → Production (ОСТОРОЖНО!) + +```bash +# Экспорт локальной БД +mysqldump -u root laravel-crm > local_db.sql + +# Загрузка на сервер +scp local_db.sql root@45.55.62.115:/tmp/ + +# Импорт на сервере +ssh root@45.55.62.115 +mysql -u styleuscrm_user -p styleuscrm_prod < /tmp/local_db.sql +rm /tmp/local_db.sql +``` + +### Production → Локальная (для тестирования) + +```bash +# На сервере создайте дамп +ssh root@45.55.62.115 "mysqldump -u styleuscrm_user -p styleuscrm_prod > /tmp/prod_db.sql" + +# Скачайте на Mac +scp root@45.55.62.115:/tmp/prod_db.sql . + +# Импортируйте локально +mysql -u root laravel-crm < prod_db.sql + +# Очистите +ssh root@45.55.62.115 "rm /tmp/prod_db.sql" +rm prod_db.sql +``` + +--- + +## 📁 Синхронизация файлов (uploads, storage) + +### Production → Локальная + +```bash +# Скачать файлы с production +rsync -avz root@45.55.62.115:/var/www/styleus/storage/app/public/ \ + ./storage/app/public/ +``` + +### Локальная → Production + +```bash +# Загрузить файлы на production +rsync -avz ./storage/app/public/ \ + root@45.55.62.115:/var/www/styleus/storage/app/public/ +``` + +--- + +## 🔐 Важные правила безопасности + +### ⚠️ НИКОГДА не синхронизируйте: + +- ❌ `.env` файлы (разные настройки для local и production) +- ❌ `vendor/` и `node_modules/` (устанавливаются через composer/npm) +- ❌ `storage/logs/` (логи разные) +- ❌ `storage/framework/cache/` (кеш разный) + +### ✅ Всегда синхронизируйте: + +- ✅ Исходный код (PHP, JS, CSS) +- ✅ Миграции базы данных +- ✅ Конфигурационные файлы +- ✅ Публичные assets + +--- + +## 🎯 Рекомендуемый workflow для вас + +**Для начала (простой):** +1. Используйте **Git + GitHub** для версионного контроля +2. Используйте **rsync скрипт** для быстрой синхронизации +3. Вручную запускайте команды на сервере после обновления + +**Когда освоитесь (продвинутый):** +1. Настройте **GitHub Actions** для автоматического deployment +2. Или используйте **Laravel Forge** для полной автоматизации + +--- + +## 📝 Быстрая шпаргалка + +### Ежедневная разработка: + +```bash +# 1. Локально: внесите изменения +# 2. Локально: тестируйте +php artisan serve + +# 3. Локально: коммит +git add . +git commit -m "Feature: описание" +git push origin main + +# 4. На сервере: обновите +ssh root@45.55.62.115 +cd /var/www/styleus +php artisan down +git pull origin main +composer install --no-dev +php artisan migrate --force +php artisan config:cache +php artisan up +``` + +### Быстрая синхронизация (без Git): + +```bash +./sync-to-production.sh +``` + +--- + +## 🆘 Откат изменений + +Если что-то пошло не так: + +```bash +# На сервере +cd /var/www/styleus +git log # посмотрите историю коммитов +git reset --hard COMMIT_HASH # откатитесь к нужному коммиту +php artisan config:cache +php artisan up +``` + +--- + +**Какой подход хотите использовать?** Рекомендую начать с Git + rsync, а потом перейти на автоматизацию. diff --git a/GOOGLE_DRIVE_SETUP.md b/GOOGLE_DRIVE_SETUP.md new file mode 100644 index 0000000000..c1377b1a73 --- /dev/null +++ b/GOOGLE_DRIVE_SETUP.md @@ -0,0 +1,553 @@ +# STYLEUS CRM - Google Drive Integration Guide + +## 📋 Полная инструкция по настройке Google Drive + +--- + +## 🎯 Что вы получите: + +- ✅ Автоматическое сохранение файлов в Google Drive +- ✅ Резервное копирование базы данных +- ✅ Доступ к файлам из любого места +- ✅ Неограниченное хранилище (Google Workspace) +- ✅ Интеграция с Gmail, Calendar, Sheets + +--- + +## Шаг 1: Создание Google Cloud Project + +### 1.1 Перейдите в Google Cloud Console + +🔗 https://console.cloud.google.com + +### 1.2 Создайте новый проект + +``` +1. Нажмите на выпадающий список проектов (вверху слева) +2. Нажмите "New Project" +3. Заполните: + - Project name: STYLEUS CRM Integration + - Organization: (ваша организация, если есть) +4. Нажмите "Create" +5. Подождите 10-20 секунд +``` + +### 1.3 Выберите созданный проект + +``` +Убедитесь, что в верхней панели выбран проект "STYLEUS CRM Integration" +``` + +--- + +## Шаг 2: Включение необходимых API + +### 2.1 Перейдите в API Library + +``` +Меню (☰) → APIs & Services → Library +``` + +### 2.2 Включите следующие API: + +Найдите и включите каждый API (нажмите "Enable"): + +#### Обязательные: +- ✅ **Google Drive API** +- ✅ **Google Sheets API** +- ✅ **Gmail API** + +#### Рекомендуемые: +- ✅ **Google Calendar API** +- ✅ **Google People API** (для контактов) +- ✅ **Google Apps Script API** + +**Для каждого API:** +``` +1. Найдите в поиске (например: "Google Drive API") +2. Нажмите на API +3. Нажмите "Enable" +4. Подождите активации +``` + +--- + +## Шаг 3: Создание OAuth 2.0 Client + +### 3.1 Настройте OAuth Consent Screen + +``` +APIs & Services → OAuth consent screen + +1. User Type: External (или Internal, если у вас Google Workspace) +2. Нажмите "Create" + +3. Заполните форму: + App name: STYLEUS CRM + User support email: admin@styleus.us + Developer contact: admin@styleus.us + +4. Нажмите "Save and Continue" + +5. Scopes - нажмите "Add or Remove Scopes": + ✅ .../auth/drive + ✅ .../auth/drive.file + ✅ .../auth/spreadsheets + ✅ .../auth/gmail.send + +6. Нажмите "Save and Continue" + +7. Test users (если External): + Добавьте ваш email: admin@styleus.us + +8. Нажмите "Save and Continue" +``` + +### 3.2 Создайте OAuth Client ID + +``` +APIs & Services → Credentials → "+ Create Credentials" → OAuth client ID + +1. Application type: Web application +2. Name: STYLEUS CRM Web Client + +3. Authorized JavaScript origins: + https://crm.styleus.us + +4. Authorized redirect URIs: + https://crm.styleus.us/auth/google/callback + https://crm.styleus.us/admin/google/callback + +5. Нажмите "Create" + +6. ВАЖНО: Скопируйте и сохраните: + - Client ID + - Client Secret +``` + +--- + +## Шаг 4: Создание Service Account + +### 4.1 Создайте Service Account + +``` +APIs & Services → Credentials → "+ Create Credentials" → Service account + +1. Service account details: + Name: styleus-crm-service + Description: Service account for STYLEUS CRM file operations + +2. Нажмите "Create and Continue" + +3. Grant this service account access to project: + Role: Project → Editor + +4. Нажмите "Continue" + +5. Нажмите "Done" +``` + +### 4.2 Создайте ключ для Service Account + +``` +1. Найдите созданный Service Account в списке +2. Нажмите на него +3. Перейдите на вкладку "Keys" +4. "Add Key" → "Create new key" +5. Key type: JSON +6. Нажмите "Create" +7. Файл автоматически скачается +8. ВАЖНО: Сохраните этот файл безопасно! +``` + +--- + +## Шаг 5: Настройка Google Drive + +### 5.1 Создайте папку в Google Drive + +``` +1. Перейдите на https://drive.google.com +2. Создайте папку: "STYLEUS CRM" +3. Внутри создайте структуру: + + STYLEUS CRM/ + ├── Leads/ + ├── Contacts/ + ├── Documents/ + ├── Quotes/ + ├── Invoices/ + └── Backups/ + ├── Database/ + └── Files/ +``` + +### 5.2 Поделитесь папкой с Service Account + +``` +1. Откройте JSON файл Service Account +2. Найдите поле "client_email" (например: styleus-crm-service@...iam.gserviceaccount.com) +3. Скопируйте этот email + +4. В Google Drive: + - Правый клик на папку "STYLEUS CRM" + - Share + - Вставьте email Service Account + - Права: Editor + - Снимите галочку "Notify people" + - Share +``` + +### 5.3 Получите ID папки + +``` +1. Откройте папку "STYLEUS CRM" в браузере +2. Скопируйте ID из URL: + + URL: https://drive.google.com/drive/folders/1a2b3c4d5e6f7g8h9i0j + ^^^^^^^^^^^^^^^^^^^^ + Это ID папки +``` + +--- + +## Шаг 6: Установка пакетов на сервере + +### 6.1 Подключитесь к серверу + +```bash +ssh root@45.55.62.115 +cd /var/www/styleus +``` + +### 6.2 Установите Google Drive пакет + +```bash +composer require nao-pon/flysystem-google-drive +composer require masbug/flysystem-google-drive-ext +``` + +--- + +## Шаг 7: Конфигурация на сервере + +### 7.1 Загрузите Service Account JSON + +На вашем Mac: + +```bash +scp /path/to/downloaded-service-account.json root@45.55.62.115:/var/www/styleus/storage/app/google-service-account.json +``` + +### 7.2 Обновите .env файл + +На сервере: + +```bash +nano /var/www/styleus/.env +``` + +Добавьте в конец файла: + +```env +# Google Drive Configuration +FILESYSTEM_DISK=google +GOOGLE_DRIVE_CLIENT_ID=your_client_id_here +GOOGLE_DRIVE_CLIENT_SECRET=your_client_secret_here +GOOGLE_DRIVE_REFRESH_TOKEN= +GOOGLE_DRIVE_FOLDER_ID=your_folder_id_here + +# Google Service Account +GOOGLE_APPLICATION_CREDENTIALS=/var/www/styleus/storage/app/google-service-account.json +``` + +Замените: +- `your_client_id_here` - Client ID из шага 3.2 +- `your_client_secret_here` - Client Secret из шага 3.2 +- `your_folder_id_here` - ID папки из шага 5.3 + +Сохраните: `Ctrl+O`, Enter, `Ctrl+X` + +### 7.3 Обновите config/filesystems.php + +```bash +nano /var/www/styleus/config/filesystems.php +``` + +Найдите секцию `'disks'` и добавьте: + +```php +'google' => [ + 'driver' => 'google', + 'clientId' => env('GOOGLE_DRIVE_CLIENT_ID'), + 'clientSecret' => env('GOOGLE_DRIVE_CLIENT_SECRET'), + 'refreshToken' => env('GOOGLE_DRIVE_REFRESH_TOKEN'), + 'folder' => env('GOOGLE_DRIVE_FOLDER_ID'), + 'teamDriveId' => env('GOOGLE_DRIVE_TEAM_DRIVE_ID'), +], +``` + +Сохраните файл. + +--- + +## Шаг 8: Получение Refresh Token + +### 8.1 Создайте временный скрипт + +```bash +nano /var/www/styleus/get-google-token.php +``` + +Вставьте: + +```php +setClientId($clientId); +$client->setClientSecret($clientSecret); +$client->setRedirectUri($redirectUri); +$client->addScope(\Google\Service\Drive::DRIVE); +$client->setAccessType('offline'); +$client->setPrompt('consent'); + +if (!isset($_GET['code'])) { + $authUrl = $client->createAuthUrl(); + echo "Visit this URL:\n\n"; + echo $authUrl . "\n\n"; + echo "Enter the authorization code: "; +} else { + $authCode = $_GET['code']; + $accessToken = $client->fetchAccessTokenWithAuthCode($authCode); + + if (isset($accessToken['refresh_token'])) { + echo "\nRefresh Token:\n"; + echo $accessToken['refresh_token'] . "\n"; + } else { + echo "Error: No refresh token received\n"; + } +} +?> +``` + +Замените `YOUR_CLIENT_ID` и `YOUR_CLIENT_SECRET`. + +### 8.2 Запустите скрипт + +```bash +php /var/www/styleus/get-google-token.php +``` + +Скопируйте URL, откройте в браузере, авторизуйтесь, скопируйте код. + +```bash +php /var/www/styleus/get-google-token.php?code=PASTE_CODE_HERE +``` + +Скопируйте Refresh Token. + +### 8.3 Добавьте Refresh Token в .env + +```bash +nano /var/www/styleus/.env +``` + +Найдите `GOOGLE_DRIVE_REFRESH_TOKEN=` и вставьте токен. + +### 8.4 Удалите временный скрипт + +```bash +rm /var/www/styleus/get-google-token.php +``` + +--- + +## Шаг 9: Тестирование + +### 9.1 Очистите кеш + +```bash +cd /var/www/styleus +php artisan config:cache +``` + +### 9.2 Протестируйте загрузку + +```bash +php artisan tinker +``` + +В tinker выполните: + +```php +Storage::disk('google')->put('test.txt', 'Hello from STYLEUS CRM!'); +Storage::disk('google')->exists('test.txt'); +// Должно вернуть: true +exit +``` + +### 9.3 Проверьте в Google Drive + +Откройте папку "STYLEUS CRM" - должен появиться файл `test.txt`. + +--- + +## Шаг 10: Настройка автоматических бэкапов + +### 10.1 Создайте команду бэкапа + +```bash +nano /var/www/styleus/app/Console/Commands/BackupToGoogleDrive.php +``` + +Вставьте: + +```php + %s', + env('DB_USERNAME'), + env('DB_PASSWORD'), + env('DB_DATABASE'), + $localPath + ); + + exec($command); + + // Upload to Google Drive + $contents = file_get_contents($localPath); + Storage::disk('google')->put('Backups/Database/' . $filename, $contents); + + // Delete local backup + unlink($localPath); + + $this->info('Backup uploaded to Google Drive: ' . $filename); + + return 0; + } +} +?> +``` + +### 10.2 Зарегистрируйте команду + +```bash +nano /var/www/styleus/app/Console/Kernel.php +``` + +Найдите метод `schedule()` и добавьте: + +```php +protected function schedule(Schedule $schedule) +{ + // Daily backup at 2 AM + $schedule->command('backup:google-drive')->dailyAt('02:00'); +} +``` + +### 10.3 Протестируйте бэкап + +```bash +php artisan backup:google-drive +``` + +Проверьте папку `STYLEUS CRM/Backups/Database/` в Google Drive. + +--- + +## ✅ Проверочный чеклист + +После завершения всех шагов проверьте: + +- [ ] Google Cloud Project создан +- [ ] Все необходимые API включены +- [ ] OAuth Client ID создан +- [ ] Service Account создан и настроен +- [ ] Папка в Google Drive создана и расшарена +- [ ] Пакеты установлены на сервере +- [ ] .env файл обновлен +- [ ] Refresh Token получен +- [ ] Тестовый файл успешно загружен +- [ ] Автоматический бэкап работает + +--- + +## 🔧 Устранение неполадок + +### Ошибка: "Invalid credentials" +```bash +# Проверьте .env файл +cat /var/www/styleus/.env | grep GOOGLE + +# Очистите кеш +php artisan config:cache +``` + +### Ошибка: "Folder not found" +```bash +# Проверьте ID папки +# Убедитесь, что папка расшарена с Service Account email +``` + +### Ошибка: "Permission denied" +```bash +# Проверьте права на файл Service Account +chmod 600 /var/www/styleus/storage/app/google-service-account.json +chown www-data:www-data /var/www/styleus/storage/app/google-service-account.json +``` + +--- + +## 📞 Полезные ссылки + +- **Google Cloud Console**: https://console.cloud.google.com +- **Google Drive**: https://drive.google.com +- **API Documentation**: https://developers.google.com/drive/api/v3/about-sdk +- **Laravel Filesystem**: https://laravel.com/docs/filesystem + +--- + +## 🎉 Готово! + +После завершения настройки: + +1. ✅ Все файлы CRM автоматически сохраняются в Google Drive +2. ✅ Ежедневные бэкапы базы данных +3. ✅ Доступ к файлам из любого места +4. ✅ Безопасное хранилище с версионированием + +**Следующий шаг:** Настройка Gmail интеграции для отправки email из CRM. + +--- + +**Документ создан:** 2025-12-01 +**Версия:** 1.0 diff --git a/PRODUCTION_DEPLOYMENT_GUIDE.md b/PRODUCTION_DEPLOYMENT_GUIDE.md new file mode 100644 index 0000000000..eb747802a0 --- /dev/null +++ b/PRODUCTION_DEPLOYMENT_GUIDE.md @@ -0,0 +1,950 @@ +# STYLEUS CRM - Production Deployment & Integration Guide + +## 📋 Table of Contents + +1. [Server Requirements & Setup](#server-requirements--setup) +2. [Domain & SSL Configuration](#domain--ssl-configuration) +3. [Application Deployment](#application-deployment) +4. [Google Workspace Integration](#google-workspace-integration) +5. [Communication Platforms](#communication-platforms) +6. [AI Services Integration](#ai-services-integration) +7. [Marketing & Sales Tools](#marketing--sales-tools) +8. [Payment Processing](#payment-processing) +9. [File Storage & Backup](#file-storage--backup) +10. [Security & Monitoring](#security--monitoring) + +--- + +## Server Requirements & Setup + +### Recommended Hosting Providers + +| Provider | Recommended Plan | Monthly Cost | Notes | +|----------|-----------------|--------------|-------| +| **DigitalOcean** | Droplet 4GB RAM | $24 | Best for Laravel apps | +| **AWS Lightsail** | 2GB RAM | $20 | Easy to scale | +| **Linode** | Dedicated 4GB | $24 | Great performance | +| **Vultr** | Cloud Compute 4GB | $24 | Good global coverage | + +### Server Specifications (Minimum) + +```yaml +Operating System: Ubuntu 22.04 LTS +CPU: 2 cores +RAM: 4GB +Storage: 80GB SSD +Bandwidth: 4TB/month +``` + +### Initial Server Setup + +```bash +# 1. Update system +sudo apt update && sudo apt upgrade -y + +# 2. Install required packages +sudo apt install -y nginx mysql-server php8.3-fpm php8.3-mysql \ + php8.3-mbstring php8.3-xml php8.3-curl php8.3-zip php8.3-gd \ + php8.3-bcmath php8.3-soap php8.3-intl redis-server supervisor \ + certbot python3-certbot-nginx git unzip + +# 3. Install Composer +curl -sS https://getcomposer.org/installer | php +sudo mv composer.phar /usr/local/bin/composer +sudo chmod +x /usr/local/bin/composer + +# 4. Install Node.js & npm +curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - +sudo apt install -y nodejs + +# 5. Secure MySQL +sudo mysql_secure_installation +``` + +--- + +## Domain & SSL Configuration + +### 1. Register Domain + +**Recommended Registrars:** +- **Namecheap** (https://namecheap.com) - $8-15/year +- **Google Domains** (https://domains.google) - $12/year +- **Cloudflare Registrar** (https://cloudflare.com) - At cost pricing + +### 2. DNS Configuration + +Point your domain to your server IP: + +``` +A Record: + Name: @ + Value: YOUR_SERVER_IP + TTL: 3600 + +A Record: + Name: www + Value: YOUR_SERVER_IP + TTL: 3600 + +CNAME Record: + Name: admin + Value: yourdomain.com + TTL: 3600 +``` + +### 3. SSL Certificate (Free with Let's Encrypt) + +```bash +# Install SSL certificate +sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com + +# Auto-renewal (cron job) +sudo certbot renew --dry-run +``` + +--- + +## Application Deployment + +### 1. Clone Repository + +```bash +# Create application directory +sudo mkdir -p /var/www/styleuscrm +sudo chown -R $USER:$USER /var/www/styleuscrm + +# Clone your repository +cd /var/www +git clone YOUR_REPOSITORY_URL styleuscrm +cd styleuscrm +``` + +### 2. Configure Environment + +```bash +# Copy environment file +cp .env.example .env + +# Edit .env with production settings +nano .env +``` + +**Production .env Configuration:** + +```env +APP_NAME=STYLEUS +APP_ENV=production +APP_KEY= # Will generate +APP_DEBUG=false +APP_URL=https://yourdomain.com + +# Database +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=styleuscrm_prod +DB_USERNAME=styleuscrm_user +DB_PASSWORD=STRONG_PASSWORD_HERE + +# Redis (for caching & queues) +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +CACHE_DRIVER=redis +SESSION_DRIVER=redis +QUEUE_CONNECTION=redis + +# Mail Configuration (using Gmail SMTP) +MAIL_MAILER=smtp +MAIL_HOST=smtp.gmail.com +MAIL_PORT=587 +MAIL_USERNAME=your-email@gmail.com +MAIL_PASSWORD=your-app-password +MAIL_ENCRYPTION=tls +MAIL_FROM_ADDRESS=your-email@gmail.com +MAIL_FROM_NAME="${APP_NAME}" + +# File Storage +FILESYSTEM_DISK=google +GOOGLE_DRIVE_CLIENT_ID= +GOOGLE_DRIVE_CLIENT_SECRET= +GOOGLE_DRIVE_REFRESH_TOKEN= +GOOGLE_DRIVE_FOLDER_ID= +``` + +### 3. Install Dependencies & Deploy + +```bash +# Install PHP dependencies (production only) +composer install --no-dev --optimize-autoloader + +# Install Node dependencies +npm ci --production + +# Build frontend assets +npm run build + +# Generate application key +php artisan key:generate + +# Create database +sudo mysql -u root -p +``` + +```sql +CREATE DATABASE styleuscrm_prod CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE USER 'styleuscrm_user'@'localhost' IDENTIFIED BY 'STRONG_PASSWORD_HERE'; +GRANT ALL PRIVILEGES ON styleuscrm_prod.* TO 'styleuscrm_user'@'localhost'; +FLUSH PRIVILEGES; +EXIT; +``` + +```bash +# Run migrations +php artisan migrate --force + +# Optimize application +php artisan config:cache +php artisan route:cache +php artisan view:cache + +# Set permissions +sudo chown -R www-data:www-data /var/www/styleuscrm +sudo chmod -R 755 /var/www/styleuscrm +sudo chmod -R 775 /var/www/styleuscrm/storage +sudo chmod -R 775 /var/www/styleuscrm/bootstrap/cache +``` + +### 4. Nginx Configuration + +Create `/etc/nginx/sites-available/styleuscrm`: + +```nginx +server { + listen 80; + listen [::]:80; + server_name yourdomain.com www.yourdomain.com; + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name yourdomain.com www.yourdomain.com; + + root /var/www/styleuscrm/public; + index index.php index.html; + + # SSL Configuration (managed by Certbot) + ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + + # Application + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ \.php$ { + fastcgi_pass unix:/var/run/php/php8.3-fpm.sock; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + include fastcgi_params; + } + + location ~ /\.(?!well-known).* { + deny all; + } +} +``` + +```bash +# Enable site +sudo ln -s /etc/nginx/sites-available/styleuscrm /etc/nginx/sites-enabled/ +sudo nginx -t +sudo systemctl restart nginx +``` + +### 5. Queue Worker (Supervisor) + +Create `/etc/supervisor/conf.d/styleuscrm-worker.conf`: + +```ini +[program:styleuscrm-worker] +process_name=%(program_name)s_%(process_num)02d +command=php /var/www/styleuscrm/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600 +autostart=true +autorestart=true +stopasgroup=true +killasgroup=true +user=www-data +numprocs=2 +redirect_stderr=true +stdout_logfile=/var/www/styleuscrm/storage/logs/worker.log +stopwaitsecs=3600 +``` + +```bash +sudo supervisorctl reread +sudo supervisorctl update +sudo supervisorctl start styleuscrm-worker:* +``` + +--- + +## Google Workspace Integration + +### Required Services + +1. **Google Cloud Project**: https://console.cloud.google.com +2. **Google Workspace Admin**: https://admin.google.com + +### Setup Steps + +#### 1. Create Google Cloud Project + +``` +1. Go to https://console.cloud.google.com +2. Click "New Project" +3. Name: "STYLEUS CRM Integration" +4. Click "Create" +``` + +#### 2. Enable Required APIs + +Navigate to **APIs & Services > Library** and enable: + +- ✅ Google Drive API +- ✅ Google Sheets API +- ✅ Google Calendar API +- ✅ Gmail API +- ✅ Google Contacts API +- ✅ Google Apps Script API +- ✅ Google People API +- ✅ Google Cloud Storage API + +#### 3. Create Service Account + +``` +1. Go to APIs & Services > Credentials +2. Click "+ CREATE CREDENTIALS" > Service Account +3. Name: "STYLEUS CRM Service" +4. Grant role: "Project > Editor" +5. Click "Done" +6. Click on created service account +7. Go to "Keys" tab +8. Add Key > Create new key > JSON +9. Download and save securely +``` + +#### 4. Enable Domain-Wide Delegation + +``` +1. Edit the service account +2. Check "Enable Google Workspace Domain-wide Delegation" +3. Note the Client ID +4. Go to Google Workspace Admin Console +5. Security > API Controls > Domain-wide Delegation +6. Add new: + - Client ID: [from service account] + - OAuth Scopes: + https://www.googleapis.com/auth/drive + https://www.googleapis.com/auth/spreadsheets + https://www.googleapis.com/auth/calendar + https://mail.google.com/ + https://www.googleapis.com/auth/contacts +``` + +#### 5. Create OAuth 2.0 Client + +``` +1. APIs & Services > Credentials +2. "+ CREATE CREDENTIALS" > OAuth 2.0 Client ID +3. Application type: Web application +4. Name: "STYLEUS CRM Web Client" +5. Authorized redirect URIs: + - https://yourdomain.com/auth/google/callback + - https://yourdomain.com/admin/google/callback +6. Click "Create" +7. Copy Client ID and Client Secret to .env +``` + +#### 6. Google Drive Setup + +```bash +# Install Google Drive package +composer require nao-pon/flysystem-google-drive +``` + +Update `config/filesystems.php`: + +```php +'google' => [ + 'driver' => 'google', + 'clientId' => env('GOOGLE_DRIVE_CLIENT_ID'), + 'clientSecret' => env('GOOGLE_DRIVE_CLIENT_SECRET'), + 'refreshToken' => env('GOOGLE_DRIVE_REFRESH_TOKEN'), + 'folder' => env('GOOGLE_DRIVE_FOLDER_ID'), +], +``` + +**Generate Refresh Token:** + +```bash +php artisan google:drive:auth +``` + +#### 7. Google Sheets Integration + +Create a dedicated folder structure: + +``` +STYLEUS CRM/ +├── Leads/ +├── Contacts/ +├── Products/ +├── Quotes/ +├── Reports/ +└── Exports/ +``` + +Share the main folder with your service account email. + +### Google Apps Script Setup + +#### Create Standalone Script + +1. Go to https://script.google.com +2. New Project > Name: "STYLEUS CRM Connector" +3. Add webhook endpoint: + +```javascript +function doPost(e) { + const data = JSON.parse(e.postData.contents); + + // Route to CRM webhook + const url = 'https://yourdomain.com/api/webhooks/google-script'; + const options = { + method: 'post', + contentType: 'application/json', + payload: JSON.stringify(data), + headers: { + 'Authorization': 'Bearer YOUR_API_TOKEN' + } + }; + + UrlFetchApp.fetch(url, options); + + return ContentService.createTextOutput(JSON.stringify({success: true})) + .setMimeType(ContentService.MimeType.JSON); +} +``` + +4. Deploy as Web App: + - Execute as: Me + - Who has access: Anyone + - Copy Web App URL to CRM config + +--- + +## Communication Platforms + +### Telegram Integration + +#### 1. Create Telegram Bot + +``` +1. Open Telegram, search for @BotFather +2. Send: /newbot +3. Choose name: STYLEUS CRM Bot +4. Choose username: styleus_crm_bot +5. Copy the API Token +``` + +#### 2. Configure Webhook + +```bash +curl -X POST "https://api.telegram.org/bot/setWebhook" \ + -d "url=https://yourdomain.com/api/webhooks/telegram" \ + -d "allowed_updates=[\"message\",\"callback_query\"]" +``` + +Add to `.env`: + +```env +TELEGRAM_BOT_TOKEN=your_bot_token_here +TELEGRAM_WEBHOOK_URL=https://yourdomain.com/api/webhooks/telegram +``` + +### WhatsApp Business API + +#### Via Twilio + +1. **Sign up**: https://www.twilio.com/console +2. **Get WhatsApp Sandbox**: Console > Messaging > Try it out > WhatsApp +3. **Activate Sandbox**: Send join code to sandbox number +4. **Production Setup**: Apply for WhatsApp Business API access + +```env +TWILIO_ACCOUNT_SID=your_account_sid +TWILIO_AUTH_TOKEN=your_auth_token +TWILIO_WHATSAPP_FROM=whatsapp:+14155238886 +TWILIO_WEBHOOK_URL=https://yourdomain.com/api/webhooks/whatsapp +``` + +#### Configure Webhook + +``` +Twilio Console > Phone Numbers > WhatsApp Sandbox Settings + When a message comes in: https://yourdomain.com/api/webhooks/whatsapp +``` + +### WhatsApp Business (Official) + +1. **Meta Business Account**: https://business.facebook.com +2. **WhatsApp Business API**: https://developers.facebook.com/products/whatsapp +3. **Requirements**: + - Verified business + - Dedicated phone number + - Official business documentation + +```env +META_APP_ID=your_app_id +META_APP_SECRET=your_app_secret +WHATSAPP_PHONE_NUMBER_ID=your_phone_number_id +WHATSAPP_BUSINESS_ACCOUNT_ID=your_business_account_id +WHATSAPP_ACCESS_TOKEN=your_permanent_access_token +``` + +### Apple Messages for Business + +1. **Register**: https://register.apple.com/business-chat +2. **Requirements**: + - Apple Business Register account + - D-U-N-S Number + - Business verification +3. **Integration**: Through messaging service providers (Twilio, Salesforce, etc.) + +```env +APPLE_MESSAGES_CSP_ID=your_csp_id +APPLE_MESSAGES_TOKEN=your_auth_token +``` + +### Zoom Integration + +1. **Create Zoom App**: https://marketplace.zoom.us/develop/create +2. **App Type**: OAuth or JWT +3. **Required Scopes**: + - `meeting:write` + - `meeting:read` + - `user:read` + +```env +ZOOM_CLIENT_ID=your_client_id +ZOOM_CLIENT_SECRET=your_client_secret +ZOOM_REDIRECT_URL=https://yourdomain.com/auth/zoom/callback +``` + +--- + +## AI Services Integration + +### OpenAI (ChatGPT) API + +1. **Sign up**: https://platform.openai.com/signup +2. **API Keys**: https://platform.openai.com/api-keys +3. **Billing**: Add payment method + +```env +OPENAI_API_KEY=sk-...your_key_here +OPENAI_ORGANIZATION=org-...your_org_id +OPENAI_MODEL=gpt-4-turbo-preview +``` + +**Usage Example:** + +```php +use OpenAI\Laravel\Facades\OpenAI; + +$result = OpenAI::chat()->create([ + 'model' => 'gpt-4', + 'messages' => [ + ['role' => 'user', 'content' => 'Summarize this lead'], + ], +]); +``` + +### Anthropic (Claude) API + +1. **Sign up**: https://console.anthropic.com +2. **Get API Key**: Account Settings > API Keys +3. **Set budget limits** + +```env +ANTHROPIC_API_KEY=sk-ant-...your_key_here +ANTHROPIC_MODEL=claude-3-opus-20240229 +``` + +```bash +composer require anthropic-ai/client +``` + +--- + +## Marketing & Sales Tools + +### Facebook & Instagram (Meta) + +#### 1. Create Meta App + +``` +1. https://developers.facebook.com/apps +2. Create App > Business Type +3. Add Products: + - Facebook Login + - Instagram Basic Display + - Marketing API +``` + +#### 2. Required Permissions + +- `pages_show_list` +- `pages_read_engagement` +- `pages_manage_posts` +- `instagram_basic` +- `instagram_manage_messages` +- `leads_retrieval` + +```env +FACEBOOK_APP_ID=your_app_id +FACEBOOK_APP_SECRET=your_app_secret +FACEBOOK_PAGE_ID=your_page_id +FACEBOOK_ACCESS_TOKEN=your_long_lived_token + +INSTAGRAM_BUSINESS_ACCOUNT_ID=your_instagram_id +INSTAGRAM_ACCESS_TOKEN=your_access_token +``` + +#### 3. Lead Ads Webhook + +``` +App Dashboard > Webhooks > Page > Subscribe to: + - leadgen (lead ads) + - messages + +Callback URL: https://yourdomain.com/api/webhooks/facebook +Verify Token: your_custom_verify_token +``` + +### Tilda Integration + +1. **Export API Key**: Tilda Project Settings > Export > API +2. **Webhook**: Forms > Webhook URL + +```env +TILDA_PUBLIC_KEY=your_public_key +TILDA_SECRET_KEY=your_secret_key +TILDA_WEBHOOK_URL=https://yourdomain.com/api/webhooks/tilda +``` + +### Calendly + +1. **Developer Access**: https://calendly.com/integrations/api_webhooks +2. **Create Webhook**: + +```env +CALENDLY_API_KEY=your_api_key +CALENDLY_WEBHOOK_SIGNING_KEY=your_signing_key +``` + +**Webhook Events:** +- `invitee.created` +- `invitee.canceled` + +--- + +## Payment Processing + +### Stripe Integration + +1. **Sign up**: https://stripe.com +2. **Get API Keys**: Dashboard > Developers > API keys +3. **Configure Webhooks**: Developers > Webhooks + +```env +STRIPE_KEY=pk_live_...your_publishable_key +STRIPE_SECRET=sk_live_...your_secret_key +STRIPE_WEBHOOK_SECRET=whsec_...your_webhook_secret +``` + +**Webhook Endpoint**: `https://yourdomain.com/api/webhooks/stripe` + +**Events to Subscribe**: +- `payment_intent.succeeded` +- `payment_intent.payment_failed` +- `customer.created` +- `invoice.paid` +- `subscription.created` + +```bash +composer require stripe/stripe-php +``` + +### Square Integration + +1. **Sign up**: https://squareup.com/signup +2. **Developer Dashboard**: https://developer.squareup.com/apps +3. **Create Application** + +```env +SQUARE_APPLICATION_ID=your_app_id +SQUARE_ACCESS_TOKEN=your_access_token +SQUARE_LOCATION_ID=your_location_id +SQUARE_WEBHOOK_SIGNATURE_KEY=your_signature_key +``` + +**Webhook Endpoint**: `https://yourdomain.com/api/webhooks/square` + +--- + +## File Storage & Backup + +### Google Drive as Primary Storage + +Already configured in [Google Workspace Integration](#google-workspace-integration). + +**Folder Structure:** + +``` +STYLEUS CRM Cloud/ +├── Leads/ +│ ├── Documents/ +│ └── Attachments/ +├── Quotes/ +│ └── PDFs/ +├── Invoices/ +├── Contracts/ +├── Backups/ +│ ├── Database/ +│ └── Files/ +└── Reports/ +``` + +### Automated Backups + +#### Database Backup Script + +Create `/var/www/styleuscrm/scripts/backup-database.sh`: + +```bash +#!/bin/bash +DATE=$(date +%Y%m%d_%H%M%S) +BACKUP_DIR="/var/www/styleuscrm/storage/backups" +DB_NAME="styleuscrm_prod" +DB_USER="styleuscrm_user" +DB_PASS="YOUR_PASSWORD" + +mkdir -p $BACKUP_DIR + +# Create backup +mysqldump -u $DB_USER -p$DB_PASS $DB_NAME | gzip > $BACKUP_DIR/db_backup_$DATE.sql.gz + +# Upload to Google Drive +php /var/www/styleuscrm/artisan backup:upload $BACKUP_DIR/db_backup_$DATE.sql.gz + +# Keep only last 7 days locally +find $BACKUP_DIR -name "*.sql.gz" -mtime +7 -delete +``` + +```bash +chmod +x /var/www/styleuscrm/scripts/backup-database.sh +``` + +#### Cron Job for Automated Backups + +```bash +sudo crontab -e +``` + +Add: + +```cron +# Database backup daily at 2 AM +0 2 * * * /var/www/styleuscrm/scripts/backup-database.sh + +# Laravel scheduled tasks +* * * * * cd /var/www/styleuscrm && php artisan schedule:run >> /dev/null 2>&1 +``` + +--- + +## Security & Monitoring + +### Firewall Configuration + +```bash +# Enable UFW +sudo ufw allow 22 +sudo ufw allow 80 +sudo ufw allow 443 +sudo ufw enable +``` + +### Fail2Ban (Brute Force Protection) + +```bash +sudo apt install fail2ban + +# Create jail for Nginx +sudo nano /etc/fail2ban/jail.local +``` + +```ini +[nginx-limit-req] +enabled = true +filter = nginx-limit-req +logpath = /var/log/nginx/error.log +maxretry = 5 +findtime = 600 +bantime = 3600 +``` + +### Application Monitoring + +#### Install Laravel Horizon (Queue Monitoring) + +```bash +composer require laravel/horizon +php artisan horizon:install +``` + +#### Logging & Error Tracking + +**Sentry** (Recommended): + +1. Sign up: https://sentry.io +2. Create project for Laravel + +```bash +composer require sentry/sentry-laravel +``` + +```env +SENTRY_LARAVEL_DSN=your_sentry_dsn +SENTRY_TRACES_SAMPLE_RATE=1.0 +``` + +### SSL & Security Headers + +Already configured in Nginx config above. Additional headers in `config/cors.php`. + +### Regular Updates + +```bash +# Create update script +nano /var/www/styleuscrm/scripts/update.sh +``` + +```bash +#!/bin/bash +cd /var/www/styleuscrm + +# Backup before update +./scripts/backup-database.sh + +# Pull latest code +git pull origin main + +# Update dependencies +composer install --no-dev +npm ci --production +npm run build + +# Run migrations +php artisan migrate --force + +# Clear & rebuild caches +php artisan config:cache +php artisan route:cache +php artisan view:cache + +# Restart services +sudo supervisorctl restart styleuscrm-worker:* +sudo systemctl reload php8.3-fpm +``` + +--- + +## 📊 Cost Summary + +### Monthly Costs (Estimated) + +| Service | Cost | Required | +|---------|------|----------| +| **Server (DigitalOcean/AWS)** | $24 | ✅ Yes | +| **Domain** | $1 | ✅ Yes | +| **SSL** | FREE (Let's Encrypt) | ✅ Yes | +| **Google Workspace** | $6/user | ✅ Yes | +| **Twilio (WhatsApp)** | $20-100 | ⚠️ Usage-based | +| **OpenAI API** | $20-200 | ⚠️ Usage-based | +| **Anthropic API** | $20-200 | ⚠️ Usage-based | +| **Stripe** | 2.9% + $0.30 | ⚠️ Per transaction | +| **Square** | 2.6% + $0.10 | ⚠️ Per transaction | +| **Zoom** | $14.99/host | 🔵 Optional | +| **Sentry** | FREE-$26 | 🔵 Optional | + +**Total Base Cost**: ~$51/month + usage fees + +--- + +## 🚀 Quick Deployment Checklist + +- [ ] Register domain and configure DNS +- [ ] Provision server (DigitalOcean/AWS/Linode) +- [ ] Install required packages (PHP, MySQL, Nginx, etc.) +- [ ] Clone repository and configure `.env` +- [ ] Install SSL certificate +- [ ] Create database and run migrations +- [ ] Configure Nginx and restart +- [ ] Set up Supervisor for queue workers +- [ ] Create Google Cloud project and enable APIs +- [ ] Configure Google Workspace integration +- [ ] Set up Google Drive storage +- [ ] Register and configure Telegram bot +- [ ] Set up Twilio for WhatsApp +- [ ] Create OpenAI and Anthropic API keys +- [ ] Configure Meta (Facebook/Instagram) app +- [ ] Set up Stripe and Square payment gateways +- [ ] Configure Tilda and Calendly webhooks +- [ ] Set up automated backups +- [ ] Enable monitoring (Sentry, Horizon) +- [ ] Configure firewall and Fail2Ban +- [ ] Test all integrations +- [ ] Document admin credentials securely + +--- + +## 📞 Support & Resources + +### Official Documentation +- **Krayin CRM**: https://devdocs.krayincrm.com +- **Laravel**: https://laravel.com/docs +- **Google Cloud**: https://cloud.google.com/docs +- **Twilio**: https://www.twilio.com/docs +- **Stripe**: https://stripe.com/docs/api + +### Community +- **Krayin Forums**: https://forums.krayincrm.com +- **Laravel Community**: https://laracasts.com + +--- + +**Document Version**: 1.0 +**Last Updated**: 2025-12-01 +**Author**: STYLEUS Development Team diff --git a/analyze-drive-structure.php b/analyze-drive-structure.php new file mode 100644 index 0000000000..6bd6317a34 --- /dev/null +++ b/analyze-drive-structure.php @@ -0,0 +1,75 @@ +#!/usr/bin/env php +load(); + +// Настройка Google Client +$client = new Client(); +$client->setClientId($_ENV['GOOGLE_CLIENT_ID']); +$client->setClientSecret($_ENV['GOOGLE_CLIENT_SECRET']); +$client->setAccessType('offline'); +$client->setScopes([Drive::DRIVE]); + +// Установка refresh token +$client->refreshToken($_ENV['GOOGLE_REFRESH_TOKEN']); + +// Создание сервиса Drive +$driveService = new Drive($client); + +// ID корневой папки компании +$rootFolderId = '0AL0zGtXFlzoiUk9PVA'; + +echo "🔍 Analyzing Google Drive structure...\n\n"; +echo "Root Folder ID: {$rootFolderId}\n"; +echo str_repeat("=", 60) . "\n\n"; + +function listFolderContents($service, $folderId, $level = 0) { + $indent = str_repeat(" ", $level); + + try { + $query = "'{$folderId}' in parents and trashed=false"; + $results = $service->files->listFiles([ + 'q' => $query, + 'fields' => 'files(id, name, mimeType, createdTime, modifiedTime)', + 'orderBy' => 'folder,name' + ]); + + $files = $results->getFiles(); + + if (empty($files)) { + echo "{$indent}📁 (empty)\n"; + return; + } + + foreach ($files as $file) { + $isFolder = $file->getMimeType() === 'application/vnd.google-apps.folder'; + $icon = $isFolder ? '📁' : '📄'; + + echo "{$indent}{$icon} {$file->getName()}\n"; + echo "{$indent} ID: {$file->getId()}\n"; + echo "{$indent} Created: {$file->getCreatedTime()}\n"; + + // Рекурсивно показываем содержимое папок (только первый уровень) + if ($isFolder && $level < 2) { + listFolderContents($service, $file->getId(), $level + 1); + } + + echo "\n"; + } + } catch (Exception $e) { + echo "{$indent}❌ Error: {$e->getMessage()}\n"; + } +} + +// Анализируем структуру +listFolderContents($driveService, $rootFolderId); + +echo str_repeat("=", 60) . "\n"; +echo "✅ Analysis complete!\n"; diff --git a/app/Http/Controllers/GoogleDriveController.php b/app/Http/Controllers/GoogleDriveController.php new file mode 100644 index 0000000000..78a8363750 --- /dev/null +++ b/app/Http/Controllers/GoogleDriveController.php @@ -0,0 +1,384 @@ +googleDriveService = $googleDriveService; + $this->leadRepository = $leadRepository; + } + + /** + * Create Google Drive folder for lead + * + * @param int $id Lead ID + * @return \Illuminate\Http\JsonResponse + */ + public function createLeadFolder($id) + { + \Log::info('createLeadFolder called for lead ID: ' . $id); + + try { + $lead = $this->leadRepository->findOrFail($id); + + \Log::info('Lead found', ['lead_id' => $lead->id, 'title' => $lead->title]); + + // Check if folder already exists + if ($lead->google_drive_folder_id) { + return response()->json([ + 'success' => false, + 'message' => 'Folder already exists for this lead', + 'folder_url' => $lead->google_drive_folder_url + ], 400); + } + + // Get or assign client number + if (!$lead->client_number) { + $clientNumber = $this->googleDriveService->getNextClientNumber(); + $lead->client_number = $clientNumber; + } else { + $clientNumber = $lead->client_number; + } + + // Get person name + $personName = $lead->person->name ?? 'Unknown'; + + // Create folder name + $folderName = $this->googleDriveService->createLeadFolderName($clientNumber, $personName); + + // Check if folder with this name already exists + $leadsFolderId = config('google.folders.leads'); + $existingFolderId = $this->googleDriveService->findFolderByName($folderName, $leadsFolderId); + + if ($existingFolderId) { + // Folder exists, link it + $lead->google_drive_folder_id = $existingFolderId; + $lead->google_drive_folder_url = $this->googleDriveService->getFolderUrl($existingFolderId); + $lead->folder_created_at = now(); + $lead->title = $folderName; // Update title to match folder name + $lead->save(); + + return response()->json([ + 'success' => true, + 'message' => 'Existing folder linked successfully', + 'folder_id' => $existingFolderId, + 'folder_url' => $lead->google_drive_folder_url, + 'client_number' => $clientNumber, + 'new_title' => $folderName + ]); + } + + // Create new folder + $folder = $this->googleDriveService->createFolder($folderName, $leadsFolderId); + + // Update lead + $lead->google_drive_folder_id = $folder['id']; + $lead->google_drive_folder_url = $folder['url']; + $lead->folder_created_at = now(); + $lead->title = $folderName; // Update title to match folder name + $lead->save(); + + return response()->json([ + 'success' => true, + 'message' => 'Folder created successfully', + 'folder_id' => $folder['id'], + 'folder_url' => $folder['url'], + 'client_number' => $clientNumber, + 'new_title' => $folderName + ]); + + } catch (\Exception $e) { + \Log::error('Failed to create lead folder: ' . $e->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => 'Failed to create folder: ' . $e->getMessage() + ], 500); + } + } + + /** + * Move lead folder to Projects directory + * + * @param int $id Lead ID + * @return \Illuminate\Http\JsonResponse + */ + public function moveToProjects($id) + { + try { + $lead = $this->leadRepository->findOrFail($id); + + if (!$lead->google_drive_folder_id) { + return response()->json([ + 'success' => false, + 'message' => 'No folder exists for this lead' + ], 400); + } + + $leadsFolderId = config('google.folders.leads'); + $projectsFolderId = config('google.folders.projects'); + + $success = $this->googleDriveService->moveFolder( + $lead->google_drive_folder_id, + $projectsFolderId, + $leadsFolderId + ); + + if ($success) { + $lead->folder_moved_at = now(); + $lead->save(); + + return response()->json([ + 'success' => true, + 'message' => 'Folder moved to Projects successfully' + ]); + } + + return response()->json([ + 'success' => false, + 'message' => 'Failed to move folder' + ], 500); + + } catch (\Exception $e) { + \Log::error('Failed to move folder: ' . $e->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => 'Failed to move folder: ' . $e->getMessage() + ], 500); + } + } + + /** + * List files in lead folder + * + * @param int $id Lead ID + * @return \Illuminate\Http\JsonResponse + */ + public function listFiles($id) + { + try { + $lead = $this->leadRepository->findOrFail($id); + + if (!$lead->google_drive_folder_id) { + return response()->json([ + 'success' => false, + 'message' => 'No folder exists for this lead', + 'files' => [] + ]); + } + + $files = $this->googleDriveService->listFiles($lead->google_drive_folder_id); + + return response()->json([ + 'success' => true, + 'files' => $files + ]); + + } catch (\Exception $e) { + \Log::error('Failed to list files: ' . $e->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => 'Failed to list files: ' . $e->getMessage(), + 'files' => [] + ], 500); + } + } + + /** + * Upload file to lead folder + * + * @param int $id Lead ID + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function uploadFile($id, Request $request) + { + try { + $lead = $this->leadRepository->findOrFail($id); + + if (!$lead->google_drive_folder_id) { + return response()->json([ + 'success' => false, + 'message' => 'No folder exists for this lead' + ], 400); + } + + $request->validate([ + 'file' => 'required|file|max:51200' // 50MB max + ]); + + $file = $request->file('file'); + $uploadedFile = $this->googleDriveService->uploadFile($file, $lead->google_drive_folder_id); + + return response()->json([ + 'success' => true, + 'message' => 'File uploaded successfully', + 'file' => $uploadedFile + ]); + + } catch (\Exception $e) { + \Log::error('Failed to upload file: ' . $e->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => 'Failed to upload file: ' . $e->getMessage() + ], 500); + } + } + + /** + * Delete file from Drive + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function deleteFile(Request $request) + { + try { + $request->validate([ + 'file_id' => 'required|string' + ]); + + $success = $this->googleDriveService->deleteFile($request->file_id); + + if ($success) { + return response()->json([ + 'success' => true, + 'message' => 'File deleted successfully' + ]); + } + + return response()->json([ + 'success' => false, + 'message' => 'Failed to delete file' + ], 500); + + } catch (\Exception $e) { + \Log::error('Failed to delete file: ' . $e->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => 'Failed to delete file: ' . $e->getMessage() + ], 500); + } + } + + /** + * Create project for lead + * + * @param int $id Lead ID + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function createProject($id, Request $request) + { + try { + $lead = $this->leadRepository->findOrFail($id); + + if (!$lead->google_drive_folder_id) { + return response()->json([ + 'success' => false, + 'message' => 'No folder exists for this lead. Create lead folder first.' + ], 400); + } + + $request->validate([ + 'project_name' => 'required|string|max:255' + ]); + + // Get next project number + $projectNumber = DB::table('lead_projects') + ->where('lead_id', $lead->id) + ->max('project_number') + 1; + + if (!$projectNumber) { + $projectNumber = 1; + } + + // Create project folder name + $folderName = $this->googleDriveService->createProjectFolderName( + $lead->client_number, + $projectNumber, + $request->project_name + ); + + // Create folder + $folder = $this->googleDriveService->createFolder($folderName, $lead->google_drive_folder_id); + + // Save project + $project = DB::table('lead_projects')->insertGetId([ + 'lead_id' => $lead->id, + 'project_number' => $projectNumber, + 'project_name' => $request->project_name, + 'google_drive_folder_id' => $folder['id'], + 'google_drive_folder_url' => $folder['url'], + 'created_at' => now(), + 'updated_at' => now() + ]); + + return response()->json([ + 'success' => true, + 'message' => 'Project created successfully', + 'project' => [ + 'id' => $project, + 'project_number' => $projectNumber, + 'project_name' => $request->project_name, + 'folder_id' => $folder['id'], + 'folder_url' => $folder['url'] + ] + ]); + + } catch (\Exception $e) { + \Log::error('Failed to create project: ' . $e->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => 'Failed to create project: ' . $e->getMessage() + ], 500); + } + } + + /** + * Get projects for lead + * + * @param int $id Lead ID + * @return \Illuminate\Http\JsonResponse + */ + public function getProjects($id) + { + try { + $projects = DB::table('lead_projects') + ->where('lead_id', $id) + ->orderBy('project_number') + ->get(); + + return response()->json([ + 'success' => true, + 'projects' => $projects + ]); + + } catch (\Exception $e) { + \Log::error('Failed to get projects: ' . $e->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => 'Failed to get projects: ' . $e->getMessage(), + 'projects' => [] + ], 500); + } + } +} diff --git a/app/Services/GoogleDriveService.php b/app/Services/GoogleDriveService.php new file mode 100644 index 0000000000..d36aa416e3 --- /dev/null +++ b/app/Services/GoogleDriveService.php @@ -0,0 +1,272 @@ +client = new Client(); + $this->client->setClientId(config('google.client_id')); + $this->client->setClientSecret(config('google.client_secret')); + $this->client->setAccessType('offline'); + $this->client->addScope(Drive::DRIVE); + + // Set refresh token and fetch access token + $refreshToken = config('google.refresh_token'); + $this->client->fetchAccessTokenWithRefreshToken($refreshToken); + + $this->driveService = new Drive($this->client); + } + + /** + * Create a folder in Google Drive + * + * @param string $name Folder name + * @param string $parentId Parent folder ID + * @return array ['id' => folder_id, 'url' => folder_url] + */ + public function createFolder(string $name, string $parentId): array + { + $fileMetadata = new DriveFile([ + 'name' => $name, + 'mimeType' => 'application/vnd.google-apps.folder', + 'parents' => [$parentId] + ]); + + $folder = $this->driveService->files->create($fileMetadata, [ + 'fields' => 'id, webViewLink', + 'supportsAllDrives' => true // Support for Shared Drives + ]); + + return [ + 'id' => $folder->id, + 'url' => $folder->webViewLink + ]; + } + + /** + * Move folder to a new parent + * + * @param string $folderId Folder ID to move + * @param string $newParentId New parent folder ID + * @param string $oldParentId Old parent folder ID + * @return bool + */ + public function moveFolder(string $folderId, string $newParentId, string $oldParentId): bool + { + try { + $this->driveService->files->update($folderId, new DriveFile(), [ + 'addParents' => $newParentId, + 'removeParents' => $oldParentId, + 'fields' => 'id, parents', + 'supportsAllDrives' => true + ]); + + return true; + } catch (Exception $e) { + \Log::error('Failed to move folder: ' . $e->getMessage()); + return false; + } + } + + /** + * List files in a folder + * + * @param string $folderId Folder ID + * @return array + */ + public function listFiles(string $folderId): array + { + $query = "'{$folderId}' in parents and trashed=false"; + + $results = $this->driveService->files->listFiles([ + 'q' => $query, + 'fields' => 'files(id, name, mimeType, size, createdTime, modifiedTime, webViewLink, thumbnailLink)', + 'orderBy' => 'folder,name', + 'supportsAllDrives' => true, + 'includeItemsFromAllDrives' => true + ]); + + $files = []; + foreach ($results->getFiles() as $file) { + $files[] = [ + 'id' => $file->id, + 'name' => $file->name, + 'mimeType' => $file->mimeType, + 'size' => $file->size, + 'createdTime' => $file->createdTime, + 'modifiedTime' => $file->modifiedTime, + 'url' => $file->webViewLink, + 'thumbnail' => $file->thumbnailLink, + 'isFolder' => $file->mimeType === 'application/vnd.google-apps.folder' + ]; + } + + return $files; + } + + /** + * Upload file to Google Drive + * + * @param \Illuminate\Http\UploadedFile $file + * @param string $folderId Parent folder ID + * @return array ['id' => file_id, 'url' => file_url] + */ + public function uploadFile($file, string $folderId): array + { + $fileMetadata = new DriveFile([ + 'name' => $file->getClientOriginalName(), + 'parents' => [$folderId] + ]); + + $content = file_get_contents($file->getRealPath()); + + $uploadedFile = $this->driveService->files->create($fileMetadata, [ + 'data' => $content, + 'mimeType' => $file->getMimeType(), + 'uploadType' => 'multipart', + 'fields' => 'id, webViewLink', + 'supportsAllDrives' => true + ]); + + return [ + 'id' => $uploadedFile->id, + 'url' => $uploadedFile->webViewLink + ]; + } + + /** + * Delete file from Google Drive + * + * @param string $fileId File ID + * @return bool + */ + public function deleteFile(string $fileId): bool + { + try { + $this->driveService->files->delete($fileId); + return true; + } catch (Exception $e) { + \Log::error('Failed to delete file: ' . $e->getMessage()); + return false; + } + } + + /** + * Get folder URL + * + * @param string $folderId Folder ID + * @return string + */ + public function getFolderUrl(string $folderId): string + { + return "https://drive.google.com/drive/folders/{$folderId}"; + } + + /** + * Get next available client number + * + * @return int + */ + public function getNextClientNumber(): int + { + // Get current number from settings table + $setting = DB::table('system_settings') + ->where('key', 'next_client_number') + ->first(); + + if (!$setting) { + // Initialize with 1316 (next after 1315) + DB::table('system_settings')->insert([ + 'key' => 'next_client_number', + 'value' => '1316', + 'created_at' => now(), + 'updated_at' => now() + ]); + return 1316; + } + + $nextNumber = (int) $setting->value; + + // Increment for next time + DB::table('system_settings') + ->where('key', 'next_client_number') + ->update([ + 'value' => $nextNumber + 1, + 'updated_at' => now() + ]); + + return $nextNumber; + } + + /** + * Format client number according to configuration + * + * @param int $number + * @return string + */ + public function formatClientNumber(int $number): string + { + $format = config('google.client_numbering.format', '%04d'); + return sprintf($format, $number); + } + + /** + * Create lead folder name + * + * @param int $clientNumber + * @param string $personName + * @return string + */ + public function createLeadFolderName(int $clientNumber, string $personName): string + { + return "{$clientNumber} - " . strtoupper($personName); + } + + /** + * Create project folder name + * + * @param int $clientNumber + * @param int $projectNumber + * @param string $projectName + * @return string + */ + public function createProjectFolderName(int $clientNumber, int $projectNumber, string $projectName): string + { + return "{$clientNumber}.{$projectNumber} - " . strtoupper($projectName); + } + + /** + * Check if folder exists by name in parent + * + * @param string $folderName + * @param string $parentId + * @return string|null Folder ID if exists, null otherwise + */ + public function findFolderByName(string $folderName, string $parentId): ?string + { + $query = "name='{$folderName}' and '{$parentId}' in parents and mimeType='application/vnd.google-apps.folder' and trashed=false"; + + $results = $this->driveService->files->listFiles([ + 'q' => $query, + 'fields' => 'files(id)', + 'pageSize' => 1, + 'supportsAllDrives' => true, + 'includeItemsFromAllDrives' => true + ]); + + $files = $results->getFiles(); + + return !empty($files) ? $files[0]->id : null; + } +} diff --git a/composer.json b/composer.json index 2c2c1914de..7d18648a97 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ "diglactic/laravel-breadcrumbs": "^8.0", "doctrine/dbal": "^3.0", "enshrined/svg-sanitize": "^0.21.0", + "google/apiclient": "^2.18", "guzzlehttp/guzzle": "^7.0.1", "khaled.alshamaa/ar-php": "^6.3", "konekt/concord": "^1.10", diff --git a/composer.lock b/composer.lock index 38745141c0..b778ff3882 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c61331a7bc1143912b7aa49b7a246780", + "content-hash": "695d15ca79a8fa67a4178e478fbb6f37", "packages": [ { "name": "barryvdh/laravel-dompdf", @@ -1379,6 +1379,69 @@ }, "time": "2020-10-16T08:27:54+00:00" }, + { + "name": "firebase/php-jwt", + "version": "v6.11.1", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^2.0||^3.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v6.11.1" + }, + "time": "2025-04-09T20:32:01+00:00" + }, { "name": "fruitcake/php-cors", "version": "v1.3.0", @@ -1450,6 +1513,181 @@ ], "time": "2023-10-12T05:21:21+00:00" }, + { + "name": "google/apiclient", + "version": "v2.18.4", + "source": { + "type": "git", + "url": "https://github.com/googleapis/google-api-php-client.git", + "reference": "5b51fdb2cbd2a96088e3dfc6f565bdf6fb0af94b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/5b51fdb2cbd2a96088e3dfc6f565bdf6fb0af94b", + "reference": "5b51fdb2cbd2a96088e3dfc6f565bdf6fb0af94b", + "shasum": "" + }, + "require": { + "firebase/php-jwt": "^6.0", + "google/apiclient-services": "~0.350", + "google/auth": "^1.37", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.6", + "monolog/monolog": "^2.9||^3.0", + "php": "^8.1", + "phpseclib/phpseclib": "^3.0.36" + }, + "require-dev": { + "cache/filesystem-adapter": "^1.1", + "composer/composer": "^1.10.23", + "phpcompatibility/php-compatibility": "^9.2", + "phpspec/prophecy-phpunit": "^2.1", + "phpunit/phpunit": "^9.6", + "squizlabs/php_codesniffer": "^3.8", + "symfony/css-selector": "~2.1", + "symfony/dom-crawler": "~2.1" + }, + "suggest": { + "cache/filesystem-adapter": "For caching certs and tokens (using Google\\Client::setCache)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "src/aliases.php" + ], + "psr-4": { + "Google\\": "src/" + }, + "classmap": [ + "src/aliases.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Client library for Google APIs", + "homepage": "http://developers.google.com/api-client-library/php", + "keywords": [ + "google" + ], + "support": { + "issues": "https://github.com/googleapis/google-api-php-client/issues", + "source": "https://github.com/googleapis/google-api-php-client/tree/v2.18.4" + }, + "time": "2025-09-30T04:23:07+00:00" + }, + { + "name": "google/apiclient-services", + "version": "v0.422.0", + "source": { + "type": "git", + "url": "https://github.com/googleapis/google-api-php-client-services.git", + "reference": "1854d325eb22e33fa6c1c0ce45718b64eca5c67b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/1854d325eb22e33fa6c1c0ce45718b64eca5c67b", + "reference": "1854d325eb22e33fa6c1c0ce45718b64eca5c67b", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6" + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ], + "psr-4": { + "Google\\Service\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Client library for Google APIs", + "homepage": "http://developers.google.com/api-client-library/php", + "keywords": [ + "google" + ], + "support": { + "issues": "https://github.com/googleapis/google-api-php-client-services/issues", + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.422.0" + }, + "time": "2025-11-30T01:02:18+00:00" + }, + { + "name": "google/auth", + "version": "v1.49.0", + "source": { + "type": "git", + "url": "https://github.com/googleapis/google-auth-library-php.git", + "reference": "68e3d88cb59a49f713e3db25d4f6bb3cc0b70764" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/68e3d88cb59a49f713e3db25d4f6bb3cc0b70764", + "reference": "68e3d88cb59a49f713e3db25d4f6bb3cc0b70764", + "shasum": "" + }, + "require": { + "firebase/php-jwt": "^6.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.4.5", + "php": "^8.1", + "psr/cache": "^2.0||^3.0", + "psr/http-message": "^1.1||^2.0", + "psr/log": "^3.0" + }, + "require-dev": { + "guzzlehttp/promises": "^2.0", + "kelvinmo/simplejwt": "0.7.1", + "phpseclib/phpseclib": "^3.0.35", + "phpspec/prophecy-phpunit": "^2.1", + "phpunit/phpunit": "^9.6", + "sebastian/comparator": ">=1.2.3", + "squizlabs/php_codesniffer": "^4.0", + "symfony/filesystem": "^6.3||^7.3", + "symfony/process": "^6.0||^7.0", + "webmozart/assert": "^1.11" + }, + "suggest": { + "phpseclib/phpseclib": "May be used in place of OpenSSL for signing strings or for token management. Please require version ^2." + }, + "type": "library", + "autoload": { + "psr-4": { + "Google\\Auth\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Google Auth Library for PHP", + "homepage": "https://github.com/google/google-auth-library-php", + "keywords": [ + "Authentication", + "google", + "oauth2" + ], + "support": { + "docs": "https://cloud.google.com/php/docs/reference/auth/latest", + "issues": "https://github.com/googleapis/google-auth-library-php/issues", + "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.49.0" + }, + "time": "2025-11-06T21:27:55+00:00" + }, { "name": "graham-campbell/result-type", "version": "v1.1.3", @@ -4134,6 +4372,75 @@ ], "time": "2024-11-21T10:36:35+00:00" }, + { + "name": "paragonie/constant_time_encoding", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", + "shasum": "" + }, + "require": { + "php": "^8" + }, + "require-dev": { + "infection/infection": "^0", + "nikic/php-fuzzer": "^0", + "phpunit/phpunit": "^9|^10|^11", + "vimeo/psalm": "^4|^5|^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2025-09-24T15:06:41+00:00" + }, { "name": "paragonie/random_compat", "version": "v9.99.100", @@ -4455,6 +4762,116 @@ ], "time": "2024-07-20T21:41:07+00:00" }, + { + "name": "phpseclib/phpseclib", + "version": "3.0.47", + "source": { + "type": "git", + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/9d6ca36a6c2dd434765b1071b2644a1c683b385d", + "reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "^1|^2|^3", + "paragonie/random_compat": "^1.4|^2.0|^9.99.99", + "php": ">=5.6.1" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "suggest": { + "ext-dom": "Install the DOM extension to load XML formatted public keys.", + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." + }, + "type": "library", + "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], + "psr-4": { + "phpseclib3\\": "phpseclib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "support": { + "issues": "https://github.com/phpseclib/phpseclib/issues", + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.47" + }, + "funding": [ + { + "url": "https://github.com/terrafrost", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpseclib", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", + "type": "tidelift" + } + ], + "time": "2025-10-06T01:07:24+00:00" + }, { "name": "prettus/l5-repository", "version": "2.10.1", @@ -11593,5 +12010,5 @@ "php": "^8.2" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/config/google.php b/config/google.php new file mode 100644 index 0000000000..a3db94aa1a --- /dev/null +++ b/config/google.php @@ -0,0 +1,21 @@ + env('GOOGLE_CLIENT_ID'), + 'client_secret' => env('GOOGLE_CLIENT_SECRET'), + 'refresh_token' => env('GOOGLE_REFRESH_TOKEN'), + 'drive_folder_id' => env('GOOGLE_DRIVE_FOLDER_ID'), + + 'folders' => [ + 'root' => env('GOOGLE_DRIVE_ROOT_FOLDER_ID'), + 'leads' => env('GOOGLE_DRIVE_LEADS_FOLDER_ID'), + 'projects' => env('GOOGLE_DRIVE_PROJECTS_FOLDER_ID'), + 'development' => env('GOOGLE_DRIVE_DEVELOPMENT_FOLDER_ID'), + 'pr' => env('GOOGLE_DRIVE_PR_FOLDER_ID'), + 'scan' => env('GOOGLE_DRIVE_SCAN_FOLDER_ID'), + ], + + 'client_numbering' => [ + 'format' => '%04d', // Format: 0001, 0002, ... 1315, 1316 + ], +]; diff --git a/create-admin.sh b/create-admin.sh new file mode 100644 index 0000000000..bd7ac0919a --- /dev/null +++ b/create-admin.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +echo "🔧 Creating admin user directly in database..." + +cd /var/www/styleus + +# Recreate DB user +./fix-database.sh + +# Create admin user via SQL +mysql styleuscrm_prod -u styleuscrm_user -pPobeda8888 << 'EOF' +INSERT INTO users (name, email, password, role_id, view_permission, status, created_at, updated_at) +VALUES ( + 'svmod', + 'svmod@styleus.us', + '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', + 1, + 'global', + 1, + NOW(), + NOW() +); +EOF + +if [ $? -eq 0 ]; then + echo "✅ Admin user created!" + echo "" + echo "Login credentials:" + echo " Email: svmod@styleus.us" + echo " Password: password" + echo "" + echo "⚠️ IMPORTANT: Change password after first login!" + echo "" + echo "🌐 Visit: https://crm.styleus.us" +else + echo "❌ Failed to create admin user" + exit 1 +fi diff --git a/database/migrations/2025_12_02_044048_add_google_drive_fields_to_leads_table.php b/database/migrations/2025_12_02_044048_add_google_drive_fields_to_leads_table.php new file mode 100644 index 0000000000..c377ee4f57 --- /dev/null +++ b/database/migrations/2025_12_02_044048_add_google_drive_fields_to_leads_table.php @@ -0,0 +1,38 @@ +integer('client_number')->nullable()->unique()->after('id'); + $table->string('google_drive_folder_id')->nullable()->after('client_number'); + $table->string('google_drive_folder_url')->nullable()->after('google_drive_folder_id'); + $table->timestamp('folder_created_at')->nullable()->after('google_drive_folder_url'); + $table->timestamp('folder_moved_at')->nullable()->after('folder_created_at'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('leads', function (Blueprint $table) { + $table->dropColumn([ + 'client_number', + 'google_drive_folder_id', + 'google_drive_folder_url', + 'folder_created_at', + 'folder_moved_at' + ]); + }); + } +}; diff --git a/database/migrations/2025_12_02_044114_create_lead_projects_table.php b/database/migrations/2025_12_02_044114_create_lead_projects_table.php new file mode 100644 index 0000000000..7bfb141e18 --- /dev/null +++ b/database/migrations/2025_12_02_044114_create_lead_projects_table.php @@ -0,0 +1,38 @@ +id(); + $table->unsignedInteger('lead_id'); // Match leads.id type (int unsigned) + $table->integer('project_number'); // 1, 2, 3... + $table->string('project_name'); + $table->string('google_drive_folder_id')->nullable(); + $table->string('google_drive_folder_url')->nullable(); + $table->timestamps(); + + // Foreign key constraint + $table->foreign('lead_id')->references('id')->on('leads')->onDelete('cascade'); + + // Ensure unique project numbers per lead + $table->unique(['lead_id', 'project_number']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('lead_projects'); + } +}; diff --git a/database/migrations/2025_12_02_044115_create_system_settings_table.php b/database/migrations/2025_12_02_044115_create_system_settings_table.php new file mode 100644 index 0000000000..0f0aa869a7 --- /dev/null +++ b/database/migrations/2025_12_02_044115_create_system_settings_table.php @@ -0,0 +1,37 @@ +id(); + $table->string('key')->unique(); + $table->text('value'); + $table->timestamps(); + }); + + // Seed with initial client number (1316 - next after 1315) + DB::table('system_settings')->insert([ + 'key' => 'next_client_number', + 'value' => '1316', + 'created_at' => now(), + 'updated_at' => now() + ]); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('system_settings'); + } +}; diff --git a/deploy-to-digitalocean.sh b/deploy-to-digitalocean.sh new file mode 100644 index 0000000000..f72b8549a5 --- /dev/null +++ b/deploy-to-digitalocean.sh @@ -0,0 +1,238 @@ +#!/bin/bash + +############################################################################### +# STYLEUS CRM - Automated Deployment Script for DigitalOcean +# Domain: crm.styleus.us +# Server IP: 45.55.62.115 +############################################################################### + +set -e + +echo "🚀 Starting STYLEUS CRM deployment..." + +# Configuration +DOMAIN="crm.styleus.us" +APP_DIR="/var/www/styleus" +DB_NAME="styleuscrm_prod" +DB_USER="styleuscrm_user" +DB_PASS=$(openssl rand -base64 32) +ADMIN_EMAIL="admin@styleus.us" +ADMIN_PASS="Pobeda88!88" + +echo "📦 Step 1/8: Updating system packages..." +apt update && apt upgrade -y + +echo "📦 Step 2/8: Installing required packages..." +apt install -y nginx mysql-server php8.3-fpm php8.3-mysql php8.3-mbstring \ + php8.3-xml php8.3-curl php8.3-zip php8.3-gd php8.3-bcmath php8.3-soap \ + php8.3-intl php8.3-readline php8.3-cli redis-server supervisor git unzip \ + certbot python3-certbot-nginx curl + +echo "📦 Step 3/8: Installing Composer..." +curl -sS https://getcomposer.org/installer | php +mv composer.phar /usr/local/bin/composer +chmod +x /usr/local/bin/composer + +echo "📦 Step 4/8: Installing Node.js & npm..." +curl -fsSL https://deb.nodesource.com/setup_20.x | bash - +apt install -y nodejs + +echo "🗄️ Step 5/8: Configuring MySQL..." +mysql -e "CREATE DATABASE ${DB_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" +mysql -e "CREATE USER '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASS}';" +mysql -e "GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'localhost';" +mysql -e "FLUSH PRIVILEGES;" + +echo "📁 Step 6/8: Setting up application directory..." +mkdir -p ${APP_DIR} +cd ${APP_DIR} + +# Note: You'll need to upload your code here +# For now, we'll create a placeholder +echo "⚠️ IMPORTANT: Upload your application code to ${APP_DIR}" +echo " You can use: scp -r /path/to/styleuscrm root@45.55.62.115:${APP_DIR}" + +# Create .env file +cat > ${APP_DIR}/.env << EOF +APP_NAME=STYLEUS +APP_ENV=production +APP_KEY= +APP_DEBUG=false +APP_URL=https://${DOMAIN} +APP_TIMEZONE=America/New_York +APP_LOCALE=en +APP_CURRENCY=USD + +LOG_CHANNEL=stack +LOG_LEVEL=error + +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=${DB_NAME} +DB_USERNAME=${DB_USER} +DB_PASSWORD=${DB_PASS} +DB_PREFIX= + +BROADCAST_DRIVER=log +CACHE_DRIVER=redis +QUEUE_CONNECTION=redis +SESSION_DRIVER=redis +SESSION_LIFETIME=120 + +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_MAILER=smtp +MAIL_HOST=smtp.gmail.com +MAIL_PORT=587 +MAIL_USERNAME= +MAIL_PASSWORD= +MAIL_ENCRYPTION=tls +MAIL_FROM_ADDRESS=${ADMIN_EMAIL} +MAIL_FROM_NAME="\${APP_NAME}" +EOF + +echo "🔒 Step 7/8: Configuring Nginx..." +cat > /etc/nginx/sites-available/styleuscrm << 'NGINX_EOF' +server { + listen 80; + listen [::]:80; + server_name crm.styleus.us; + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name crm.styleus.us; + + root /var/www/styleuscrm/public; + index index.php index.html; + + # SSL Configuration (will be added by Certbot) + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + + # Logging + access_log /var/log/nginx/styleuscrm-access.log; + error_log /var/log/nginx/styleuscrm-error.log; + + # Application + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ \.php$ { + fastcgi_pass unix:/var/run/php/php8.3-fpm.sock; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + include fastcgi_params; + fastcgi_hide_header X-Powered-By; + } + + location ~ /\.(?!well-known).* { + deny all; + } + + location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } +} +NGINX_EOF + +ln -sf /etc/nginx/sites-available/styleuscrm /etc/nginx/sites-enabled/ +rm -f /etc/nginx/sites-enabled/default +nginx -t + +echo "🔐 Step 8/8: Setting up SSL certificate..." +echo "⚠️ Make sure DNS for crm.styleus.us is pointing to this server before continuing!" +read -p "Press Enter when DNS is ready (or Ctrl+C to cancel)..." + +certbot --nginx -d ${DOMAIN} --non-interactive --agree-tos --email ${ADMIN_EMAIL} --redirect + +echo "⚙️ Configuring Supervisor for queue workers..." +cat > /etc/supervisor/conf.d/styleuscrm-worker.conf << 'SUPERVISOR_EOF' +[program:styleuscrm-worker] +process_name=%(program_name)s_%(process_num)02d +command=php /var/www/styleuscrm/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600 +autostart=true +autorestart=true +stopasgroup=true +killasgroup=true +user=www-data +numprocs=2 +redirect_stderr=true +stdout_logfile=/var/www/styleuscrm/storage/logs/worker.log +stopwaitsecs=3600 +SUPERVISOR_EOF + +supervisorctl reread +supervisorctl update + +echo "🔄 Setting up cron jobs..." +(crontab -l 2>/dev/null; echo "* * * * * cd ${APP_DIR} && php artisan schedule:run >> /dev/null 2>&1") | crontab - + +echo "🔒 Setting permissions..." +chown -R www-data:www-data ${APP_DIR} +chmod -R 755 ${APP_DIR} +chmod -R 775 ${APP_DIR}/storage 2>/dev/null || true +chmod -R 775 ${APP_DIR}/bootstrap/cache 2>/dev/null || true + +echo "🔥 Configuring firewall..." +ufw allow 22 +ufw allow 80 +ufw allow 443 +ufw --force enable + +echo "" +echo "✅ Server setup complete!" +echo "" +echo "📋 IMPORTANT INFORMATION:" +echo "================================" +echo "Domain: https://${DOMAIN}" +echo "Database Name: ${DB_NAME}" +echo "Database User: ${DB_USER}" +echo "Database Password: ${DB_PASS}" +echo "" +echo "⚠️ SAVE THIS PASSWORD SECURELY!" +echo "" +echo "📝 Next steps:" +echo "1. Upload your application code to: ${APP_DIR}" +echo "2. Run: cd ${APP_DIR} && composer install --no-dev" +echo "3. Run: npm ci --production && npm run build" +echo "4. Run: php artisan key:generate" +echo "5. Run: php artisan migrate --force" +echo "6. Run: php artisan krayin-crm:install" +echo "" +echo "🚀 After completing these steps, visit: https://${DOMAIN}" +echo "" + +# Save credentials to file +cat > /root/styleuscrm-credentials.txt << EOF +STYLEUS CRM Credentials +======================= +Domain: https://${DOMAIN} +Server IP: 45.55.62.115 + +Database: +- Name: ${DB_NAME} +- User: ${DB_USER} +- Password: ${DB_PASS} + +Admin: +- Email: ${ADMIN_EMAIL} +- Password: ${ADMIN_PASS} + +Application Directory: ${APP_DIR} + +Generated: $(date) +EOF + +echo "💾 Credentials saved to: /root/styleuscrm-credentials.txt" diff --git a/final-setup.sh b/final-setup.sh new file mode 100644 index 0000000000..1fe683d266 --- /dev/null +++ b/final-setup.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +############################################################################### +# STYLEUS CRM - Final Setup Script +# Завершает установку CRM после развертывания кода +############################################################################### + +set -e + +echo "🚀 Starting STYLEUS CRM final setup..." + +cd /var/www/styleus + +# Update .env file +echo "📝 Updating .env configuration..." +sed -i "s/APP_NAME=.*/APP_NAME=STYLEUS/" .env +sed -i "s/APP_ENV=.*/APP_ENV=production/" .env +sed -i "s/APP_DEBUG=.*/APP_DEBUG=false/" .env +sed -i "s|APP_URL=.*|APP_URL=https://crm.styleus.us|" .env +sed -i "s/APP_TIMEZONE=.*/APP_TIMEZONE=America\/New_York/" .env +sed -i "s/DB_DATABASE=.*/DB_DATABASE=styleuscrm_prod/" .env +sed -i "s/DB_USERNAME=.*/DB_USERNAME=styleuscrm_user/" .env +sed -i "s/DB_PASSWORD=.*/DB_PASSWORD=Pobeda8888/" .env + +echo "✅ .env updated!" + +# Clear config cache +echo "🔄 Clearing configuration cache..." +php artisan config:clear + +# Run migrations +echo "🗄️ Running database migrations..." +php artisan migrate --force + +echo "✅ Migrations complete!" + +# Set permissions +echo "🔒 Setting correct permissions..." +chown -R www-data:www-data /var/www/styleus +chmod -R 755 /var/www/styleus +chmod -R 775 /var/www/styleus/storage +chmod -R 775 /var/www/styleus/bootstrap/cache + +echo "✅ Permissions set!" + +# Restart services +echo "🔄 Restarting services..." +systemctl restart php8.3-fpm +systemctl restart nginx + +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "✅ Setup complete!" +echo "" +echo "📋 Next step: Run Krayin CRM installer" +echo " Command: php artisan krayin-crm:install" +echo "" +echo " Use these details:" +echo " - Application name: STYLEUS" +echo " - Application URL: https://crm.styleus.us" +echo " - Default Locale: English" +echo " - Default Currency: USD" +echo " - Admin Name: svmod" +echo " - Admin Email: svmod@styleus.us" +echo " - Admin Password: Idlikepobeda88" +echo "" +echo "🌐 After installation, visit: https://crm.styleus.us" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" diff --git a/fix-database.sh b/fix-database.sh new file mode 100644 index 0000000000..15d5b04b0d --- /dev/null +++ b/fix-database.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +echo "🔧 Fixing database user and permissions..." + +# Drop and recreate user to ensure clean state +mysql -e "DROP USER IF EXISTS 'styleuscrm_user'@'localhost';" +mysql -e "CREATE USER 'styleuscrm_user'@'localhost' IDENTIFIED BY 'Pobeda8888';" +mysql -e "GRANT ALL PRIVILEGES ON styleuscrm_prod.* TO 'styleuscrm_user'@'localhost';" +mysql -e "FLUSH PRIVILEGES;" + +echo "✅ Database user recreated!" +echo "" +echo "Testing connection..." +mysql -u styleuscrm_user -pPobeda8888 -e "SHOW DATABASES;" 2>/dev/null + +if [ $? -eq 0 ]; then + echo "✅ Connection successful!" +else + echo "❌ Connection failed!" + exit 1 +fi + +echo "" +echo "Now run: ./final-setup.sh" diff --git a/packages/Webkul/Admin/src/Resources/lang/en/app.php b/packages/Webkul/Admin/src/Resources/lang/en/app.php index 9693d2bdd2..af092bd6ab 100644 --- a/packages/Webkul/Admin/src/Resources/lang/en/app.php +++ b/packages/Webkul/Admin/src/Resources/lang/en/app.php @@ -2094,9 +2094,20 @@ ], 'tags' => [ - 'create-success' => 'Tag created successfully.', + 'create-success' => 'Tag created successfully.', 'destroy-success' => 'Tag deleted successfully.', ], + + 'google-drive' => [ + 'create-folder' => 'Создать папку Drive', + 'open-drive' => 'Открыть в Google Drive', + 'move-to-projects' => 'Переместить в Проекты', + 'projects' => 'Проекты', + 'add-project' => 'Добавить проект', + 'confirm-create' => 'Создать папку Google Drive для этого лида?', + 'confirm-move' => 'Переместить папку в Projects? Это действие нельзя отменить.', + 'project-name-prompt' => 'Введите название проекта:', + ], ], ], diff --git a/packages/Webkul/Admin/src/Resources/views/components/activities/google-drive-files.blade.php b/packages/Webkul/Admin/src/Resources/views/components/activities/google-drive-files.blade.php new file mode 100644 index 0000000000..6472c3598e --- /dev/null +++ b/packages/Webkul/Admin/src/Resources/views/components/activities/google-drive-files.blade.php @@ -0,0 +1,94 @@ +{{-- Google Drive Files Section for Activities Tab --}} +@if($activity->type === 'file' && $activity->lead && $activity->lead->google_drive_folder_id) +
+
+

+ + Google Drive Files +

+ +
+ + + + + + +
+ + +@endif diff --git a/packages/Webkul/Admin/src/Resources/views/leads/view.blade.php b/packages/Webkul/Admin/src/Resources/views/leads/view.blade.php index 8b79070abf..1649377bf8 100644 --- a/packages/Webkul/Admin/src/Resources/views/leads/view.blade.php +++ b/packages/Webkul/Admin/src/Resources/views/leads/view.blade.php @@ -92,6 +92,9 @@ @include ('admin::leads.view.person') + + {{-- Google Drive --}} + @include ('admin::leads.view.google-drive') {!! view_render_event('admin.leads.view.left.after', ['lead' => $lead]) !!} @@ -114,6 +117,7 @@ ['name' => 'description', 'label' => trans('admin::app.leads.view.tabs.description')], ['name' => 'products', 'label' => trans('admin::app.leads.view.tabs.products')], ['name' => 'quotes', 'label' => trans('admin::app.leads.view.tabs.quotes')], + ['name' => 'google-drive', 'label' => 'Google Drive'], ]" > @@ -132,6 +136,11 @@ {{ $lead->description }} + + + + @include ('admin::leads.view.google-drive-files-vue') + {!! view_render_event('admin.leads.view.activities.after', ['lead' => $lead]) !!} diff --git a/packages/Webkul/Admin/src/Resources/views/leads/view/google-drive-files-vue.blade.php b/packages/Webkul/Admin/src/Resources/views/leads/view/google-drive-files-vue.blade.php new file mode 100644 index 0000000000..57722fb249 --- /dev/null +++ b/packages/Webkul/Admin/src/Resources/views/leads/view/google-drive-files-vue.blade.php @@ -0,0 +1,222 @@ +@props([ + 'lead', +]) + +{!! view_render_event('admin.leads.view.google_drive_files.before', ['lead' => $lead]) !!} + + + + +
+
+
+

Loading files...

+
+
+
+ +{!! view_render_event('admin.leads.view.google_drive_files.after', ['lead' => $lead]) !!} + +@pushOnce('scripts') + + + +@endPushOnce diff --git a/packages/Webkul/Admin/src/Resources/views/leads/view/google-drive-files.blade.php b/packages/Webkul/Admin/src/Resources/views/leads/view/google-drive-files.blade.php new file mode 100644 index 0000000000..214f8d399a --- /dev/null +++ b/packages/Webkul/Admin/src/Resources/views/leads/view/google-drive-files.blade.php @@ -0,0 +1,251 @@ +{{-- Google Drive Files Tab --}} +
+ @if(!$lead->google_drive_folder_id) + {{-- No Drive Folder Yet --}} +
+ +

+ Google Drive folder not created yet +

+

+ Create a Drive folder from the left panel to see files here +

+
+ @else + {{-- Header with Reload Button --}} +
+

+ Files from Google Drive +

+ +
+ + {{-- Loading State --}} +
+
+
+

Loading files from Google Drive...

+
+
+ + {{-- Files List --}} + + + {{-- Empty State --}} + + @endif +
+ +{{-- JavaScript to Load Files --}} + diff --git a/packages/Webkul/Admin/src/Resources/views/leads/view/google-drive.blade.php b/packages/Webkul/Admin/src/Resources/views/leads/view/google-drive.blade.php new file mode 100644 index 0000000000..2b84cb2953 --- /dev/null +++ b/packages/Webkul/Admin/src/Resources/views/leads/view/google-drive.blade.php @@ -0,0 +1,388 @@ +{{-- Google Drive Section --}} +
+
+

+ + Google Drive +

+ + @if($lead->client_number) + + #{{ $lead->client_number }} + + @endif +
+ + {{-- Drive Actions --}} +
+ @if(!$lead->google_drive_folder_id) + {{-- Create Folder Button --}} + + @else + {{-- Open in Drive Button --}} + + + @lang('admin::app.leads.view.google-drive.open-drive') + + + {{-- Move to Projects Button (if not moved yet) --}} + @if(!$lead->folder_moved_at && $lead->lead_pipeline_stage_id == config('lead.won_stage_id')) + + @endif + + {{-- Files Count --}} +
+ + ... files + +
+ + {{-- Collapsible Files List --}} + + @endif +
+ + {{-- Projects Section --}} + @if($lead->google_drive_folder_id) +
+
+

+ @lang('admin::app.leads.view.google-drive.projects') +

+ +
+ +
+ {{-- Projects will be loaded via JavaScript --}} +
+
+ @endif +
+ +{{-- JavaScript for Google Drive Actions --}} + diff --git a/packages/Webkul/Admin/src/Routes/Admin/leads-routes.php b/packages/Webkul/Admin/src/Routes/Admin/leads-routes.php index 43a0be06fc..43c96f8cce 100644 --- a/packages/Webkul/Admin/src/Routes/Admin/leads-routes.php +++ b/packages/Webkul/Admin/src/Routes/Admin/leads-routes.php @@ -61,4 +61,21 @@ Route::controller(QuoteController::class)->prefix('{id}/quotes')->group(function () { Route::delete('{quote_id?}', 'delete')->name('admin.leads.quotes.delete'); }); + + // Google Drive routes + Route::controller(\App\Http\Controllers\GoogleDriveController::class)->prefix('{id}/drive')->group(function () { + Route::post('create-folder', 'createLeadFolder')->name('admin.leads.drive.create_folder'); + + Route::post('move-to-projects', 'moveToProjects')->name('admin.leads.drive.move_to_projects'); + + Route::get('files', 'listFiles')->name('admin.leads.drive.list_files'); + + Route::post('upload', 'uploadFile')->name('admin.leads.drive.upload'); + + Route::delete('files', 'deleteFile')->name('admin.leads.drive.delete_file'); + + Route::get('projects', 'getProjects')->name('admin.leads.drive.get_projects'); + + Route::post('projects', 'createProject')->name('admin.leads.drive.create_project'); + }); }); diff --git a/routes/web.php b/routes/web.php index b13039731c..7f9650946d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -16,3 +16,10 @@ Route::get('/', function () { return view('welcome'); }); + +// Test route for Google Drive +Route::get('/test-drive/{id?}', function ($id = 1) { + $lead = \Webkul\Lead\Repositories\LeadRepository::class; + $lead = app($lead)->findOrFail($id); + return view('test-drive', compact('lead')); +}); diff --git a/sync-to-production.sh b/sync-to-production.sh new file mode 100755 index 0000000000..894e743f9e --- /dev/null +++ b/sync-to-production.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +############################################################################### +# STYLEUS CRM - Quick Sync to Production +# Синхронизирует локальные изменения с production сервером +############################################################################### + +SERVER="root@45.55.62.115" +REMOTE_PATH="/var/www/styleus" +GITHUB_REPO="https://github.com/rsv-tech/styleuscrm.git" + +echo "🔄 Syncing STYLEUS CRM to production..." +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + +# Sync files +rsync -avz --progress \ + --exclude 'node_modules' \ + --exclude 'vendor' \ + --exclude '.git' \ + --exclude 'storage/logs/*' \ + --exclude 'storage/framework/cache/*' \ + --exclude 'storage/framework/sessions/*' \ + --exclude 'storage/framework/views/*' \ + --exclude '.env' \ + --exclude '.env.backup' \ + --exclude '*.log' \ + . ${SERVER}:${REMOTE_PATH}/ + +echo "" +echo "✅ Files synced!" +echo "🔄 Updating server..." + +# Run commands on server +ssh ${SERVER} << 'ENDSSH' +cd /var/www/styleus + +# Clear caches +php artisan config:cache +php artisan route:cache +php artisan view:cache + +# Restart queue workers +sudo supervisorctl restart styleuscrm-worker:* 2>/dev/null || true + +echo "✅ Server updated!" +ENDSSH + +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "🎉 Deployment complete!" +echo "🌐 Visit: https://crm.styleus.us" +echo ""