Compare commits
6 Commits
a84810c6b9
...
main
Author | SHA1 | Date | |
---|---|---|---|
8191775647 | |||
e49559c0b4 | |||
26276b3f27 | |||
8ee7bc896d | |||
162d96e9aa | |||
c0e91bba1d |
@ -1,269 +0,0 @@
|
|||||||
# Настройка базы данных и системы управления новостями
|
|
||||||
|
|
||||||
## Обзор системы
|
|
||||||
|
|
||||||
Система управления новостями для ckeproekt.ru включает:
|
|
||||||
|
|
||||||
- **База данных**: PostgreSQL с Prisma ORM
|
|
||||||
- **API**: REST API и GraphQL endpoints
|
|
||||||
- **Админ-панель**: Полнофункциональная панель управления
|
|
||||||
- **Безопасность**: JWT аутентификация и авторизация
|
|
||||||
- **Функциональность**: CRUD операции, пагинация, поиск, фильтрация
|
|
||||||
|
|
||||||
## Требования
|
|
||||||
|
|
||||||
- Node.js 18+
|
|
||||||
- PostgreSQL 12+
|
|
||||||
- npm или yarn
|
|
||||||
|
|
||||||
## Установка и настройка
|
|
||||||
|
|
||||||
### 1. Установка зависимостей
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Настройка базы данных
|
|
||||||
|
|
||||||
1. Создайте PostgreSQL базу данных:
|
|
||||||
```sql
|
|
||||||
CREATE DATABASE ckeproject;
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Обновите файл `.env` с вашими данными:
|
|
||||||
```env
|
|
||||||
DATABASE_URL="postgresql://username:password@localhost:5432/ckeproject?schema=public"
|
|
||||||
NEXTAUTH_SECRET="your-secret-key-here"
|
|
||||||
NEXTAUTH_URL="http://localhost:3000"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Инициализация Prisma
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Генерация Prisma Client
|
|
||||||
npm run db:generate
|
|
||||||
|
|
||||||
# Применение схемы к базе данных
|
|
||||||
npm run db:push
|
|
||||||
|
|
||||||
# Заполнение базы данных начальными данными
|
|
||||||
npm run db:seed
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Запуск приложения
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Структура базы данных
|
|
||||||
|
|
||||||
### Таблица `users`
|
|
||||||
- `id` - Уникальный идентификатор
|
|
||||||
- `email` - Email (уникальный)
|
|
||||||
- `username` - Имя пользователя (уникальное)
|
|
||||||
- `password` - Хешированный пароль
|
|
||||||
- `role` - Роль (USER, ADMIN, EDITOR)
|
|
||||||
- `name` - Полное имя
|
|
||||||
- `avatar` - URL аватара
|
|
||||||
- `createdAt` - Дата создания
|
|
||||||
- `updatedAt` - Дата обновления
|
|
||||||
|
|
||||||
### Таблица `news`
|
|
||||||
- `id` - Уникальный идентификатор
|
|
||||||
- `title` - Заголовок новости
|
|
||||||
- `slug` - URL-слаг (уникальный)
|
|
||||||
- `summary` - Краткое описание
|
|
||||||
- `content` - Полное содержание
|
|
||||||
- `category` - Категория новости
|
|
||||||
- `imageUrl` - URL изображения
|
|
||||||
- `featured` - Рекомендуемая новость
|
|
||||||
- `published` - Статус публикации
|
|
||||||
- `publishedAt` - Дата публикации
|
|
||||||
- `authorId` - ID автора
|
|
||||||
- `views` - Количество просмотров
|
|
||||||
- `likes` - Количество лайков
|
|
||||||
- `tags` - Массив тегов
|
|
||||||
- `createdAt` - Дата создания
|
|
||||||
- `updatedAt` - Дата обновления
|
|
||||||
|
|
||||||
### Таблица `categories`
|
|
||||||
- `id` - Уникальный идентификатор
|
|
||||||
- `name` - Название категории
|
|
||||||
- `slug` - URL-слаг (уникальный)
|
|
||||||
- `description` - Описание
|
|
||||||
- `color` - Цвет категории
|
|
||||||
- `createdAt` - Дата создания
|
|
||||||
- `updatedAt` - Дата обновления
|
|
||||||
|
|
||||||
## API Endpoints
|
|
||||||
|
|
||||||
### REST API
|
|
||||||
|
|
||||||
#### Новости
|
|
||||||
- `GET /api/news` - Получить список новостей
|
|
||||||
- `POST /api/news` - Создать новость
|
|
||||||
- `GET /api/news/[id]` - Получить новость по ID
|
|
||||||
- `PUT /api/news/[id]` - Обновить новость
|
|
||||||
- `DELETE /api/news/[id]` - Удалить новость
|
|
||||||
|
|
||||||
#### Категории
|
|
||||||
- `GET /api/categories` - Получить список категорий
|
|
||||||
- `POST /api/categories` - Создать категорию
|
|
||||||
- `PUT /api/categories/[id]` - Обновить категорию
|
|
||||||
- `DELETE /api/categories/[id]` - Удалить категорию
|
|
||||||
|
|
||||||
#### Аутентификация
|
|
||||||
- `POST /api/auth/login` - Вход в систему
|
|
||||||
- `POST /api/auth/register` - Регистрация
|
|
||||||
- `POST /api/auth/logout` - Выход из системы
|
|
||||||
|
|
||||||
### GraphQL API
|
|
||||||
|
|
||||||
GraphQL endpoint доступен по адресу `/api/graphql`
|
|
||||||
|
|
||||||
#### Примеры запросов
|
|
||||||
|
|
||||||
```graphql
|
|
||||||
# Получить список новостей
|
|
||||||
query {
|
|
||||||
newsList(page: 1, limit: 10, category: "company") {
|
|
||||||
news {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
summary
|
|
||||||
publishedAt
|
|
||||||
author {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
total
|
|
||||||
totalPages
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Создать новость
|
|
||||||
mutation {
|
|
||||||
createNews(input: {
|
|
||||||
title: "Новая новость"
|
|
||||||
slug: "novaya-novost"
|
|
||||||
summary: "Краткое описание"
|
|
||||||
content: "Полное содержание"
|
|
||||||
category: "company"
|
|
||||||
featured: true
|
|
||||||
}) {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Админ-панель
|
|
||||||
|
|
||||||
Админ-панель доступна по адресу `/admin`
|
|
||||||
|
|
||||||
### Пользователи по умолчанию
|
|
||||||
|
|
||||||
После выполнения `npm run db:seed` будут созданы:
|
|
||||||
|
|
||||||
1. **Администратор**
|
|
||||||
- Email: `admin@ckeproekt.ru`
|
|
||||||
- Пароль: `admin123`
|
|
||||||
- Роль: ADMIN
|
|
||||||
|
|
||||||
2. **Редактор**
|
|
||||||
- Email: `editor@ckeproekt.ru`
|
|
||||||
- Пароль: `editor123`
|
|
||||||
- Роль: EDITOR
|
|
||||||
|
|
||||||
### Функциональность админ-панели
|
|
||||||
|
|
||||||
- ✅ Создание, редактирование и удаление новостей
|
|
||||||
- ✅ Управление категориями
|
|
||||||
- ✅ Загрузка изображений
|
|
||||||
- ✅ Визуальный редактор содержимого
|
|
||||||
- ✅ Система тегов
|
|
||||||
- ✅ Управление публикацией
|
|
||||||
- ✅ Поиск и фильтрация
|
|
||||||
- ✅ Пагинация
|
|
||||||
- ✅ Статистика
|
|
||||||
|
|
||||||
## Безопасность
|
|
||||||
|
|
||||||
### Аутентификация
|
|
||||||
- JWT токены для авторизации
|
|
||||||
- Хеширование паролей с bcrypt
|
|
||||||
- Защищенные API endpoints
|
|
||||||
|
|
||||||
### Авторизация
|
|
||||||
- Роли пользователей (USER, ADMIN, EDITOR)
|
|
||||||
- Проверка прав доступа на уровне API
|
|
||||||
- Middleware для защиты маршрутов
|
|
||||||
|
|
||||||
### Валидация
|
|
||||||
- Проверка входных данных
|
|
||||||
- Санитизация контента
|
|
||||||
- Защита от XSS и SQL инъекций
|
|
||||||
|
|
||||||
## Развертывание в продакшн
|
|
||||||
|
|
||||||
### 1. Настройка переменных окружения
|
|
||||||
|
|
||||||
```env
|
|
||||||
DATABASE_URL="postgresql://user:password@host:5432/database"
|
|
||||||
NEXTAUTH_SECRET="strong-secret-key"
|
|
||||||
NEXTAUTH_URL="https://your-domain.com"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Сборка приложения
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Миграция базы данных
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run db:migrate
|
|
||||||
npm run db:seed
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Запуск с PM2
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run pm2:start
|
|
||||||
```
|
|
||||||
|
|
||||||
## Мониторинг и обслуживание
|
|
||||||
|
|
||||||
### Prisma Studio
|
|
||||||
Для просмотра и редактирования данных:
|
|
||||||
```bash
|
|
||||||
npm run db:studio
|
|
||||||
```
|
|
||||||
|
|
||||||
### Логи
|
|
||||||
Логи доступны через PM2:
|
|
||||||
```bash
|
|
||||||
pm2 logs
|
|
||||||
```
|
|
||||||
|
|
||||||
### Резервное копирование
|
|
||||||
Регулярно создавайте резервные копии базы данных:
|
|
||||||
```bash
|
|
||||||
pg_dump ckeproject > backup.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
## Миграция с существующей системы
|
|
||||||
|
|
||||||
Скрипт `scripts/init-database.ts` автоматически мигрирует данные из `lib/news-data.ts` в базу данных.
|
|
||||||
|
|
||||||
## Поддержка
|
|
||||||
|
|
||||||
Для получения поддержки или сообщения об ошибках обращайтесь к разработчикам системы.
|
|
||||||
|
|
||||||
## Лицензия
|
|
||||||
|
|
||||||
Система разработана специально для ckeproekt.ru и является собственностью компании.
|
|
21
Dockerfile
21
Dockerfile
@ -7,8 +7,14 @@ WORKDIR /app
|
|||||||
# Copy package files
|
# Copy package files
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
||||||
# Install dependencies
|
# Install all dependencies (including dev dependencies for build)
|
||||||
RUN npm install
|
RUN npm ci
|
||||||
|
|
||||||
|
# Copy prisma schema
|
||||||
|
COPY prisma ./prisma/
|
||||||
|
|
||||||
|
# Generate Prisma client
|
||||||
|
RUN npx prisma generate
|
||||||
|
|
||||||
# Copy project files
|
# Copy project files
|
||||||
COPY . .
|
COPY . .
|
||||||
@ -23,11 +29,22 @@ WORKDIR /app
|
|||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
# Create non-root user
|
||||||
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
|
RUN adduser --system --uid 1001 nextjs
|
||||||
|
|
||||||
# Copy necessary files from builder
|
# Copy necessary files from builder
|
||||||
COPY --from=builder /app/next.config.js ./
|
COPY --from=builder /app/next.config.js ./
|
||||||
COPY --from=builder /app/public ./public
|
COPY --from=builder /app/public ./public
|
||||||
COPY --from=builder /app/.next/static ./.next/static
|
COPY --from=builder /app/.next/static ./.next/static
|
||||||
COPY --from=builder /app/.next/standalone ./
|
COPY --from=builder /app/.next/standalone ./
|
||||||
|
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
|
||||||
|
COPY --from=builder /app/prisma ./prisma
|
||||||
|
|
||||||
|
# Set correct permissions
|
||||||
|
RUN chown -R nextjs:nodejs /app
|
||||||
|
|
||||||
|
USER nextjs
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
|
@ -1,194 +0,0 @@
|
|||||||
# Итоговый отчет: Система управления новостями для ckeproekt.ru
|
|
||||||
|
|
||||||
## ✅ Выполненные задачи
|
|
||||||
|
|
||||||
### 1. Создание модели News в базе данных ✅
|
|
||||||
|
|
||||||
**Файлы:**
|
|
||||||
- `prisma/schema.prisma` - Схема базы данных
|
|
||||||
- `lib/database.ts` - Утилиты для работы с базой данных
|
|
||||||
|
|
||||||
**Реализованная модель News:**
|
|
||||||
```prisma
|
|
||||||
model News {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
title String
|
|
||||||
slug String @unique
|
|
||||||
summary String
|
|
||||||
content String @db.Text
|
|
||||||
category String
|
|
||||||
imageUrl String?
|
|
||||||
featured Boolean @default(false)
|
|
||||||
published Boolean @default(true)
|
|
||||||
publishedAt DateTime @default(now())
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
authorId String?
|
|
||||||
author User? @relation(fields: [authorId], references: [id])
|
|
||||||
views Int @default(0)
|
|
||||||
likes Int @default(0)
|
|
||||||
tags String[]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Дополнительные модели:**
|
|
||||||
- `User` - Пользователи системы с ролями (USER, ADMIN, EDITOR)
|
|
||||||
- `Category` - Категории новостей
|
|
||||||
- Связи между моделями через foreign keys
|
|
||||||
|
|
||||||
### 2. Разработка GraphQL API ✅
|
|
||||||
|
|
||||||
**Файлы:**
|
|
||||||
- `lib/graphql/schema.ts` - GraphQL схема
|
|
||||||
- `lib/graphql/resolvers.ts` - Резолверы для GraphQL
|
|
||||||
|
|
||||||
**Реализованный функционал:**
|
|
||||||
- Queries: получение новостей, списка новостей, категорий, пользователей
|
|
||||||
- Mutations: создание, обновление, удаление новостей и категорий
|
|
||||||
- Аутентификация и авторизация в GraphQL
|
|
||||||
- Пагинация и фильтрация в GraphQL запросах
|
|
||||||
- Поиск по содержимому новостей
|
|
||||||
|
|
||||||
### 3. Интеграция в существующую MS ✅
|
|
||||||
|
|
||||||
**Файлы:**
|
|
||||||
- `app/api/news/route.ts` - REST API для новостей
|
|
||||||
- `app/api/news/[id]/route.ts` - API для отдельной новости
|
|
||||||
- `lib/database.ts` - Сервисы для работы с базой данных
|
|
||||||
- `scripts/init-database.ts` - Скрипт миграции данных
|
|
||||||
|
|
||||||
**Реализованная интеграция:**
|
|
||||||
- REST API endpoints для всех операций с новостями
|
|
||||||
- Миграция существующих данных из статических файлов
|
|
||||||
- Совместимость с существующими компонентами
|
|
||||||
- Обратная совместимость с текущим интерфейсом
|
|
||||||
|
|
||||||
### 4. Обеспечение безопасности ✅
|
|
||||||
|
|
||||||
**Файлы:**
|
|
||||||
- `lib/auth.ts` - Система аутентификации и авторизации
|
|
||||||
- Middleware для защиты API endpoints
|
|
||||||
|
|
||||||
**Реализованные меры безопасности:**
|
|
||||||
- JWT токены для аутентификации
|
|
||||||
- Хеширование паролей с bcrypt
|
|
||||||
- Роли пользователей (USER, ADMIN, EDITOR)
|
|
||||||
- Middleware для проверки прав доступа
|
|
||||||
- Защита API endpoints от несанкционированного доступа
|
|
||||||
- Валидация входных данных
|
|
||||||
|
|
||||||
### 5. Создание всех необходимых страниц и компонентов ✅
|
|
||||||
|
|
||||||
**Обновленные файлы:**
|
|
||||||
- `app/admin/news/page.tsx` - Админ-панель управления новостями
|
|
||||||
- `app/admin/news/create/page.tsx` - Создание новостей
|
|
||||||
- `app/admin/news/[id]/edit/page.tsx` - Редактирование новостей
|
|
||||||
- `app/news/page.tsx` - Публичная страница новостей
|
|
||||||
- `app/news/[slug]/page.tsx` - Страница отдельной новости
|
|
||||||
- `app/components/NewsBlock.tsx` - Блок новостей на главной
|
|
||||||
|
|
||||||
**Функциональность админ-панели:**
|
|
||||||
- Создание, редактирование и удаление новостей
|
|
||||||
- Управление статусом публикации
|
|
||||||
- Система тегов
|
|
||||||
- Загрузка изображений
|
|
||||||
- Визуальный редактор
|
|
||||||
- Поиск и фильтрация
|
|
||||||
- Пагинация
|
|
||||||
- Статистика
|
|
||||||
|
|
||||||
## 🔧 Техническая архитектура
|
|
||||||
|
|
||||||
### База данных
|
|
||||||
- **PostgreSQL** с **Prisma ORM**
|
|
||||||
- Миграции и схемы версионируются
|
|
||||||
- Индексы для оптимизации запросов
|
|
||||||
- Связи между таблицами
|
|
||||||
|
|
||||||
### API
|
|
||||||
- **REST API** для основных операций
|
|
||||||
- **GraphQL API** для сложных запросов
|
|
||||||
- Единообразная обработка ошибок
|
|
||||||
- Валидация данных
|
|
||||||
|
|
||||||
### Безопасность
|
|
||||||
- **JWT** аутентификация
|
|
||||||
- **bcrypt** для хеширования паролей
|
|
||||||
- **RBAC** (Role-Based Access Control)
|
|
||||||
- Защита от XSS и SQL инъекций
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
- **Next.js 15** с **TypeScript**
|
|
||||||
- **React Server Components**
|
|
||||||
- **Tailwind CSS** для стилизации
|
|
||||||
- Адаптивный дизайн
|
|
||||||
|
|
||||||
## 📊 Функциональность системы
|
|
||||||
|
|
||||||
### Для администраторов:
|
|
||||||
- ✅ Полное управление новостями
|
|
||||||
- ✅ Управление пользователями
|
|
||||||
- ✅ Статистика и аналитика
|
|
||||||
- ✅ Настройка категорий
|
|
||||||
- ✅ Модерация контента
|
|
||||||
|
|
||||||
### Для редакторов:
|
|
||||||
- ✅ Создание и редактирование новостей
|
|
||||||
- ✅ Управление своими публикациями
|
|
||||||
- ✅ Работа с черновиками
|
|
||||||
- ✅ Загрузка медиа-файлов
|
|
||||||
|
|
||||||
### Для посетителей:
|
|
||||||
- ✅ Просмотр опубликованных новостей
|
|
||||||
- ✅ Поиск по содержимому
|
|
||||||
- ✅ Фильтрация по категориям
|
|
||||||
- ✅ Пагинация результатов
|
|
||||||
- ✅ Адаптивный дизайн
|
|
||||||
|
|
||||||
## 🚀 Развертывание
|
|
||||||
|
|
||||||
### Файлы конфигурации:
|
|
||||||
- `package.json` - Обновлен с необходимыми зависимостями
|
|
||||||
- `.env` - Переменные окружения
|
|
||||||
- `DATABASE_SETUP.md` - Подробная инструкция по развертыванию
|
|
||||||
|
|
||||||
### Скрипты:
|
|
||||||
- `npm run db:generate` - Генерация Prisma Client
|
|
||||||
- `npm run db:push` - Применение схемы к БД
|
|
||||||
- `npm run db:seed` - Заполнение начальными данными
|
|
||||||
- `npm run db:studio` - Prisma Studio для управления данными
|
|
||||||
|
|
||||||
### Пользователи по умолчанию:
|
|
||||||
- **Администратор**: admin@ckeproekt.ru / admin123
|
|
||||||
- **Редактор**: editor@ckeproekt.ru / editor123
|
|
||||||
|
|
||||||
## 📈 Результаты
|
|
||||||
|
|
||||||
### Полностью функционирующая система:
|
|
||||||
1. ✅ **База данных** настроена и готова к работе
|
|
||||||
2. ✅ **API** реализован и протестирован
|
|
||||||
3. ✅ **Админ-панель** полностью функциональна
|
|
||||||
4. ✅ **Безопасность** обеспечена на всех уровнях
|
|
||||||
5. ✅ **Интеграция** с существующим сайтом выполнена
|
|
||||||
6. ✅ **Миграция данных** из статических файлов завершена
|
|
||||||
|
|
||||||
### Преимущества новой системы:
|
|
||||||
- **Масштабируемость**: Легко добавлять новые функции
|
|
||||||
- **Производительность**: Оптимизированные запросы к БД
|
|
||||||
- **Безопасность**: Многоуровневая защита
|
|
||||||
- **Удобство**: Интуитивная админ-панель
|
|
||||||
- **SEO**: Оптимизированные URL и метаданные
|
|
||||||
|
|
||||||
## 🔄 Миграция с существующей системы
|
|
||||||
|
|
||||||
Система автоматически мигрирует данные из `lib/news-data.ts` в базу данных при выполнении команды `npm run db:seed`.
|
|
||||||
|
|
||||||
## 📞 Поддержка
|
|
||||||
|
|
||||||
Система полностью интегрирована в ckeproekt.ru и готова к использованию. Все компоненты протестированы и оптимизированы для производственной среды.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Статус проекта**: ✅ ЗАВЕРШЕН
|
|
||||||
**Дата завершения**: $(date)
|
|
||||||
**Версия**: 1.0.0
|
|
@ -51,21 +51,30 @@ export default function ImageUpload({
|
|||||||
setError('');
|
setError('');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// В реальном приложении здесь будет загрузка на сервер
|
const formData = new FormData();
|
||||||
// Для демонстрации используем FileReader для создания data URL
|
formData.append('file', file);
|
||||||
const reader = new FileReader();
|
formData.append('folder', 'images');
|
||||||
reader.onload = (e) => {
|
|
||||||
const result = e.target?.result as string;
|
// Если есть старое изображение, передаем его URL для удаления
|
||||||
onChange(result);
|
if (value && value.startsWith('http')) {
|
||||||
setIsUploading(false);
|
formData.append('oldUrl', value);
|
||||||
};
|
}
|
||||||
reader.onerror = () => {
|
|
||||||
setError('Ошибка при загрузке файла');
|
const response = await fetch('/api/upload', {
|
||||||
setIsUploading(false);
|
method: 'POST',
|
||||||
};
|
body: formData,
|
||||||
reader.readAsDataURL(file);
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(result.error || 'Ошибка при загрузке файла');
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(result.data.publicUrl);
|
||||||
|
setIsUploading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError('Ошибка при загрузке файла');
|
setError(error instanceof Error ? error.message : 'Ошибка при загрузке файла');
|
||||||
setIsUploading(false);
|
setIsUploading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -97,7 +106,19 @@ export default function ImageUpload({
|
|||||||
setDragActive(false);
|
setDragActive(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemove = () => {
|
const handleRemove = async () => {
|
||||||
|
try {
|
||||||
|
// Если файл находится в S3, удаляем его оттуда
|
||||||
|
if (value && value.startsWith('http')) {
|
||||||
|
await fetch(`/api/upload?url=${encodeURIComponent(value)}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при удалении файла:', error);
|
||||||
|
// Не показываем ошибку пользователю, так как файл может быть удален из UI
|
||||||
|
}
|
||||||
|
|
||||||
onRemove();
|
onRemove();
|
||||||
setError('');
|
setError('');
|
||||||
if (fileInputRef.current) {
|
if (fileInputRef.current) {
|
||||||
|
141
app/admin/components/S3Status.tsx
Normal file
141
app/admin/components/S3Status.tsx
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { CheckCircle, XCircle, AlertCircle, RefreshCw } from 'lucide-react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
|
||||||
|
interface S3StatusProps {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function S3Status({ className = '' }: S3StatusProps) {
|
||||||
|
const [status, setStatus] = useState<'checking' | 'connected' | 'error' | 'unknown'>('unknown');
|
||||||
|
const [error, setError] = useState<string>('');
|
||||||
|
const [isChecking, setIsChecking] = useState(false);
|
||||||
|
|
||||||
|
console.log('S3Status: Компонент инициализирован (версия 2.0)');
|
||||||
|
|
||||||
|
const checkS3Connection = async () => {
|
||||||
|
setIsChecking(true);
|
||||||
|
setStatus('checking');
|
||||||
|
setError('');
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('S3Status: Проверяю подключение к S3...');
|
||||||
|
console.log('S3Status: Используется API /api/test-s3');
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 секунд таймаут
|
||||||
|
|
||||||
|
const response = await fetch(`/api/test-s3?t=${Date.now()}`, {
|
||||||
|
signal: controller.signal,
|
||||||
|
cache: 'no-cache'
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('S3Status: Запрос отправлен к /api/test-s3, статус:', response.status);
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
console.log('S3Status: Результат проверки:', result);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
setStatus('connected');
|
||||||
|
console.log('S3Status: Подключение успешно');
|
||||||
|
} else {
|
||||||
|
setStatus('error');
|
||||||
|
const errorMessage = result.error || 'Ошибка подключения к S3';
|
||||||
|
setError(errorMessage);
|
||||||
|
console.error('S3Status: Ошибка подключения:', result);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('S3Status: Исключение при проверке:', err);
|
||||||
|
setStatus('error');
|
||||||
|
|
||||||
|
if (err instanceof Error && err.name === 'AbortError') {
|
||||||
|
setError('Таймаут подключения к S3 (более 10 секунд)');
|
||||||
|
} else {
|
||||||
|
setError(err instanceof Error ? err.message : 'Ошибка сети при проверке S3');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setIsChecking(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
checkS3Connection();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getStatusIcon = () => {
|
||||||
|
switch (status) {
|
||||||
|
case 'checking':
|
||||||
|
return <RefreshCw className="h-4 w-4 animate-spin text-blue-500" />;
|
||||||
|
case 'connected':
|
||||||
|
return <CheckCircle className="h-4 w-4 text-green-500" />;
|
||||||
|
case 'error':
|
||||||
|
return <XCircle className="h-4 w-4 text-red-500" />;
|
||||||
|
default:
|
||||||
|
return <AlertCircle className="h-4 w-4 text-gray-500" />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusText = () => {
|
||||||
|
switch (status) {
|
||||||
|
case 'checking':
|
||||||
|
return 'Проверка подключения...';
|
||||||
|
case 'connected':
|
||||||
|
return 'S3 подключено';
|
||||||
|
case 'error':
|
||||||
|
return 'Ошибка S3';
|
||||||
|
default:
|
||||||
|
return 'Статус неизвестен';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusColor = () => {
|
||||||
|
switch (status) {
|
||||||
|
case 'checking':
|
||||||
|
return 'text-blue-400';
|
||||||
|
case 'connected':
|
||||||
|
return 'text-green-400';
|
||||||
|
case 'error':
|
||||||
|
return 'text-red-400';
|
||||||
|
default:
|
||||||
|
return 'text-gray-400';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`flex items-center space-x-2 ${className}`}>
|
||||||
|
{getStatusIcon()}
|
||||||
|
<span className={`text-sm font-medium ${getStatusColor()}`}>
|
||||||
|
{getStatusText()}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{status === 'error' && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={checkS3Connection}
|
||||||
|
disabled={isChecking}
|
||||||
|
className="ml-2"
|
||||||
|
>
|
||||||
|
{isChecking ? (
|
||||||
|
<RefreshCw className="h-3 w-3 animate-spin" />
|
||||||
|
) : (
|
||||||
|
'Повторить'
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="ml-2 text-xs text-red-400 max-w-xs" title={error}>
|
||||||
|
<span className="block truncate">{error}</span>
|
||||||
|
<span className="block text-xs text-gray-500 mt-1">
|
||||||
|
Проверьте консоль для подробностей
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -4,6 +4,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import { Building, FileText, Settings, LogOut, Menu, X } from 'lucide-react';
|
import { Building, FileText, Settings, LogOut, Menu, X } from 'lucide-react';
|
||||||
|
import S3Status from './components/S3Status';
|
||||||
|
|
||||||
interface AdminLayoutProps {
|
interface AdminLayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@ -17,28 +18,43 @@ export default function AdminLayout({ children }: AdminLayoutProps) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Проверяем авторизацию
|
const checkAuth = async () => {
|
||||||
const adminAuth = localStorage.getItem('adminAuth');
|
try {
|
||||||
if (adminAuth) {
|
const res = await fetch('/api/admin/me', { cache: 'no-store' });
|
||||||
setIsAuthenticated(true);
|
setIsAuthenticated(res.ok);
|
||||||
}
|
} catch (e) {
|
||||||
setIsLoading(false);
|
setIsAuthenticated(false);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
checkAuth();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleLogin = (username: string, password: string) => {
|
const handleLogin = async (email: string, password: string) => {
|
||||||
// Простая проверка (в реальном проекте должна быть серверная авторизация)
|
try {
|
||||||
if (username === 'admin' && password === 'admin123') {
|
const res = await fetch('/api/admin/login', {
|
||||||
localStorage.setItem('adminAuth', JSON.stringify({ username, role: 'admin' }));
|
method: 'POST',
|
||||||
setIsAuthenticated(true);
|
headers: { 'Content-Type': 'application/json' },
|
||||||
return true;
|
body: JSON.stringify({ identifier: email, password })
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
setIsAuthenticated(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = async () => {
|
||||||
localStorage.removeItem('adminAuth');
|
try {
|
||||||
setIsAuthenticated(false);
|
await fetch('/api/admin/logout', { method: 'POST' });
|
||||||
router.push('/admin');
|
} finally {
|
||||||
|
setIsAuthenticated(false);
|
||||||
|
router.push('/admin');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigation = [
|
const navigation = [
|
||||||
@ -69,17 +85,17 @@ export default function AdminLayout({ children }: AdminLayoutProps) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<div className={`fixed inset-y-0 left-0 z-50 w-64 bg-white shadow-lg transform transition-transform duration-300 ease-in-out lg:translate-x-0 ${
|
<div className={`fixed inset-y-0 left-0 z-50 w-64 bg-gradient-to-b from-gray-900 to-gray-800 shadow-xl transform transition-transform duration-300 ease-in-out lg:translate-x-0 ${
|
||||||
isSidebarOpen ? 'translate-x-0' : '-translate-x-full'
|
isSidebarOpen ? 'translate-x-0' : '-translate-x-full'
|
||||||
}`}>
|
}`}>
|
||||||
<div className="flex items-center justify-between h-16 px-6 border-b">
|
<div className="flex items-center justify-between h-16 px-6 border-b border-gray-700">
|
||||||
<Link href="/admin" className="flex items-center space-x-2">
|
<Link href="/admin" className="flex items-center space-x-2">
|
||||||
<Building className="h-8 w-8 text-blue-600" />
|
<Building className="h-8 w-8 text-blue-400" />
|
||||||
<span className="text-xl font-bold text-gray-900">Admin Panel</span>
|
<span className="text-xl font-bold text-white">CKE Admin</span>
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsSidebarOpen(false)}
|
onClick={() => setIsSidebarOpen(false)}
|
||||||
className="lg:hidden p-2 rounded-md hover:bg-gray-100"
|
className="lg:hidden p-2 rounded-md hover:bg-gray-700 text-gray-300 hover:text-white"
|
||||||
>
|
>
|
||||||
<X className="h-5 w-5" />
|
<X className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
@ -92,10 +108,10 @@ export default function AdminLayout({ children }: AdminLayoutProps) {
|
|||||||
<Link
|
<Link
|
||||||
key={item.name}
|
key={item.name}
|
||||||
href={item.href}
|
href={item.href}
|
||||||
className={`flex items-center px-6 py-3 text-sm font-medium transition-colors ${
|
className={`flex items-center px-6 py-3 text-sm font-medium transition-all duration-200 ${
|
||||||
isActive
|
isActive
|
||||||
? 'bg-blue-50 text-blue-700 border-r-2 border-blue-600'
|
? 'bg-blue-600 text-white border-r-4 border-blue-400 shadow-lg'
|
||||||
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'
|
: 'text-gray-300 hover:bg-gray-700 hover:text-white'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setIsSidebarOpen(false)}
|
onClick={() => setIsSidebarOpen(false)}
|
||||||
>
|
>
|
||||||
@ -106,10 +122,13 @@ export default function AdminLayout({ children }: AdminLayoutProps) {
|
|||||||
})}
|
})}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div className="absolute bottom-0 w-full p-6 border-t">
|
<div className="absolute bottom-0 w-full p-6 border-t border-gray-700 space-y-4">
|
||||||
|
<div className="bg-gray-800 rounded-lg p-3">
|
||||||
|
<S3Status />
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
className="flex items-center w-full px-3 py-2 text-sm font-medium text-red-600 hover:bg-red-50 rounded-md transition-colors"
|
className="flex items-center w-full px-3 py-2 text-sm font-medium text-red-400 hover:bg-red-900 hover:bg-opacity-20 rounded-md transition-colors"
|
||||||
>
|
>
|
||||||
<LogOut className="mr-3 h-5 w-5" />
|
<LogOut className="mr-3 h-5 w-5" />
|
||||||
Выйти
|
Выйти
|
||||||
@ -120,17 +139,22 @@ export default function AdminLayout({ children }: AdminLayoutProps) {
|
|||||||
{/* Main content */}
|
{/* Main content */}
|
||||||
<div className="lg:ml-64">
|
<div className="lg:ml-64">
|
||||||
{/* Top bar */}
|
{/* Top bar */}
|
||||||
<div className="bg-white shadow-sm border-b">
|
<div className="bg-white shadow-sm border-b border-gray-200">
|
||||||
<div className="flex items-center justify-between h-16 px-4 sm:px-6 lg:px-8">
|
<div className="flex items-center justify-between h-16 px-4 sm:px-6 lg:px-8">
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsSidebarOpen(true)}
|
onClick={() => setIsSidebarOpen(true)}
|
||||||
className="lg:hidden p-2 rounded-md hover:bg-gray-100"
|
className="lg:hidden p-2 rounded-md hover:bg-gray-100 text-gray-600"
|
||||||
>
|
>
|
||||||
<Menu className="h-5 w-5" />
|
<Menu className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<span className="text-sm text-gray-500">Добро пожаловать, admin</span>
|
<div className="flex items-center space-x-2">
|
||||||
|
<div className="w-8 h-8 bg-gradient-to-r from-blue-500 to-blue-600 rounded-full flex items-center justify-center">
|
||||||
|
<span className="text-white text-sm font-bold">A</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-sm text-gray-700 font-medium">Добро пожаловать, admin</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -145,8 +169,8 @@ export default function AdminLayout({ children }: AdminLayoutProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Компонент формы входа
|
// Компонент формы входа
|
||||||
function LoginForm({ onLogin }: { onLogin: (username: string, password: string) => boolean }) {
|
function LoginForm({ onLogin }: { onLogin: (email: string, password: string) => Promise<boolean> }) {
|
||||||
const [username, setUsername] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
@ -154,38 +178,41 @@ function LoginForm({ onLogin }: { onLogin: (username: string, password: string)
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setError('');
|
setError('');
|
||||||
|
|
||||||
if (!username || !password) {
|
if (!email || !password) {
|
||||||
setError('Заполните все поля');
|
setError('Заполните все поля');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const success = onLogin(username, password);
|
onLogin(email, password).then((success) => {
|
||||||
if (!success) {
|
if (!success) setError('Неверный логин или пароль');
|
||||||
setError('Неверный логин или пароль');
|
});
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
|
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center p-4">
|
||||||
<div className="max-w-md w-full bg-white rounded-lg shadow-md p-8">
|
<div className="max-w-md w-full bg-white rounded-xl shadow-xl p-8 border border-gray-100">
|
||||||
<div className="text-center mb-8">
|
<div className="text-center mb-8">
|
||||||
<Building className="h-12 w-12 text-blue-600 mx-auto mb-4" />
|
<div className="w-16 h-16 bg-gradient-to-r from-blue-600 to-blue-700 rounded-full flex items-center justify-center mx-auto mb-4 shadow-lg">
|
||||||
<h1 className="text-2xl font-bold text-gray-900">Административная панель</h1>
|
<Building className="h-8 w-8 text-white" />
|
||||||
|
</div>
|
||||||
|
<h1 className="text-2xl font-bold bg-gradient-to-r from-gray-900 to-gray-700 bg-clip-text text-transparent">
|
||||||
|
CKE Admin Panel
|
||||||
|
</h1>
|
||||||
<p className="text-gray-600 mt-2">Войдите в систему управления</p>
|
<p className="text-gray-600 mt-2">Войдите в систему управления</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-6">
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="username" className="block text-sm font-medium text-gray-700 mb-2">
|
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
Логин
|
Email или логин
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="username"
|
id="email"
|
||||||
type="text"
|
type="text"
|
||||||
value={username}
|
value={email}
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
placeholder="Введите логин"
|
placeholder="Введите email или логин"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { ArrowLeft, Save, Eye, Upload, X, Trash2 } from 'lucide-react';
|
import { ArrowLeft, Save, Eye, Upload, X, Trash2 } from 'lucide-react';
|
||||||
import { NEWS_CATEGORIES, NewsFormData, NewsCategory } from '@/lib/types';
|
import { NewsFormData, NewsCategory } from '@/lib/types';
|
||||||
// import { getNewsById } from '@/lib/news-data';
|
// import { getNewsById } from '@/lib/news-data';
|
||||||
|
|
||||||
interface EditNewsPageProps {
|
interface EditNewsPageProps {
|
||||||
@ -35,8 +35,24 @@ export default function EditNewsPage({ params }: EditNewsPageProps) {
|
|||||||
|
|
||||||
const [errors, setErrors] = useState<Partial<NewsFormData>>({});
|
const [errors, setErrors] = useState<Partial<NewsFormData>>({});
|
||||||
const [tagInput, setTagInput] = useState('');
|
const [tagInput, setTagInput] = useState('');
|
||||||
|
const [categories, setCategories] = useState<{ id: string; name: string }[]>([
|
||||||
|
{ id: 'company', name: 'Новости компании' },
|
||||||
|
{ id: 'promotions', name: 'Акции' },
|
||||||
|
{ id: 'other', name: 'Другое' }
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// подгружаем категории
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/categories', { cache: 'no-store' });
|
||||||
|
const data = await res.json();
|
||||||
|
if (res.ok && data?.data?.length) {
|
||||||
|
setCategories(data.data.map((c: any) => ({ id: c.slug, name: c.name })));
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
})();
|
||||||
|
|
||||||
const loadNews = async () => {
|
const loadNews = async () => {
|
||||||
const resolvedParams = await params;
|
const resolvedParams = await params;
|
||||||
const id = resolvedParams.id;
|
const id = resolvedParams.id;
|
||||||
@ -300,7 +316,7 @@ export default function EditNewsPage({ params }: EditNewsPageProps) {
|
|||||||
onChange={(e) => setFormData(prev => ({ ...prev, category: e.target.value as NewsCategory }))}
|
onChange={(e) => setFormData(prev => ({ ...prev, category: e.target.value as NewsCategory }))}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
>
|
>
|
||||||
{NEWS_CATEGORIES.map((category) => (
|
{categories.map((category) => (
|
||||||
<option key={category.id} value={category.id}>
|
<option key={category.id} value={category.id}>
|
||||||
{category.name}
|
{category.name}
|
||||||
</option>
|
</option>
|
||||||
@ -506,10 +522,8 @@ export default function EditNewsPage({ params }: EditNewsPageProps) {
|
|||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div className="flex items-center space-x-2 mb-4">
|
<div className="flex items-center space-x-2 mb-4">
|
||||||
<span className={`px-2 py-1 rounded-full text-xs font-medium text-white ${
|
<span className={`px-2 py-1 rounded-full text-xs font-medium text-white bg-gray-500`}>
|
||||||
NEWS_CATEGORIES.find(cat => cat.id === formData.category)?.color || 'bg-gray-500'
|
{categories.find(cat => cat.id === formData.category)?.name || 'Категория'}
|
||||||
}`}>
|
|
||||||
{NEWS_CATEGORIES.find(cat => cat.id === formData.category)?.name}
|
|
||||||
</span>
|
</span>
|
||||||
{formData.featured && (
|
{formData.featured && (
|
||||||
<span className="px-2 py-1 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
|
<span className="px-2 py-1 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { ArrowLeft, Save, Eye, Upload, X } from 'lucide-react';
|
import { ArrowLeft, Save, X } from 'lucide-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import TextEditor from '@/app/admin/components/TextEditor';
|
import TextEditor from '@/app/admin/components/TextEditor';
|
||||||
import ImageUpload from '@/app/admin/components/ImageUpload';
|
import ImageUpload from '@/app/admin/components/ImageUpload';
|
||||||
|
|
||||||
const NEWS_CATEGORIES = [
|
interface UiCategory { id: string; name: string }
|
||||||
{ id: 'company', name: 'Новости компании' },
|
|
||||||
{ id: 'promotions', name: 'Акции' },
|
|
||||||
{ id: 'other', name: 'Другое' }
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function CreateNewsPage() {
|
export default function CreateNewsPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -29,6 +25,25 @@ export default function CreateNewsPage() {
|
|||||||
tags: [] as string[]
|
tags: [] as string[]
|
||||||
});
|
});
|
||||||
const [tagInput, setTagInput] = useState('');
|
const [tagInput, setTagInput] = useState('');
|
||||||
|
const [categories, setCategories] = useState<UiCategory[]>([
|
||||||
|
{ id: 'company', name: 'Новости компании' },
|
||||||
|
{ id: 'promotions', name: 'Акции' },
|
||||||
|
{ id: 'other', name: 'Другое' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/categories', { cache: 'no-store' });
|
||||||
|
const data = await res.json();
|
||||||
|
if (res.ok && data?.data?.length) {
|
||||||
|
setCategories(data.data.map((c: any) => ({ id: c.slug, name: c.name })));
|
||||||
|
// если выбранная категория отсутствует — выставим первую
|
||||||
|
setFormData(prev => ({ ...prev, category: data.data[0]?.slug || prev.category }));
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const generateSlug = (title: string) => {
|
const generateSlug = (title: string) => {
|
||||||
return title
|
return title
|
||||||
@ -96,11 +111,29 @@ export default function CreateNewsPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveAsDraft = async () => {
|
const handleSaveAsDraft = async () => {
|
||||||
setFormData(prev => ({ ...prev, published: false }));
|
const draftData = { ...formData, published: false };
|
||||||
// Используем setTimeout чтобы дождаться обновления состояния
|
setFormData(draftData);
|
||||||
setTimeout(() => {
|
|
||||||
handleSubmit(new Event('submit') as any);
|
// Сохраняем как черновик
|
||||||
}, 0);
|
try {
|
||||||
|
const response = await fetch('/api/news', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
...draftData,
|
||||||
|
publishedAt: new Date(draftData.publishedAt).toISOString()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.success) {
|
||||||
|
router.push('/admin/news');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving draft:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -220,7 +253,7 @@ export default function CreateNewsPage() {
|
|||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
{NEWS_CATEGORIES.map((category) => (
|
{categories.map((category) => (
|
||||||
<option key={category.id} value={category.id}>
|
<option key={category.id} value={category.id}>
|
||||||
{category.name}
|
{category.name}
|
||||||
</option>
|
</option>
|
||||||
|
@ -33,13 +33,14 @@ interface NewsCategory {
|
|||||||
color: string;
|
color: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NEWS_CATEGORIES: NewsCategory[] = [
|
const DEFAULT_CATEGORIES: NewsCategory[] = [
|
||||||
{ id: 'company', name: 'Новости компании', slug: 'company', color: 'bg-blue-500' },
|
{ id: 'company', name: 'Новости компании', slug: 'company', color: 'bg-blue-500' },
|
||||||
{ id: 'promotions', name: 'Акции', slug: 'promotions', color: 'bg-green-500' },
|
{ id: 'promotions', name: 'Акции', slug: 'promotions', color: 'bg-green-500' },
|
||||||
{ id: 'other', name: 'Другое', slug: 'other', color: 'bg-purple-500' }
|
{ id: 'other', name: 'Другое', slug: 'other', color: 'bg-purple-500' }
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function AdminNewsPage() {
|
export default function AdminNewsPage() {
|
||||||
|
const [categories, setCategories] = useState<NewsCategory[]>(DEFAULT_CATEGORIES);
|
||||||
const [news, setNews] = useState<NewsItem[]>([]);
|
const [news, setNews] = useState<NewsItem[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
@ -52,6 +53,18 @@ export default function AdminNewsPage() {
|
|||||||
loadNews();
|
loadNews();
|
||||||
}, [searchQuery, selectedCategory, selectedStatus, sortBy, sortOrder]);
|
}, [searchQuery, selectedCategory, selectedStatus, sortBy, sortOrder]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/categories', { cache: 'no-store' });
|
||||||
|
const data = await res.json();
|
||||||
|
if (res.ok && data?.data?.length) {
|
||||||
|
setCategories(data.data.map((c: any) => ({ id: c.slug, name: c.name, slug: c.slug, color: c.color || 'bg-blue-500' })));
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const loadNews = async () => {
|
const loadNews = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@ -102,9 +115,7 @@ export default function AdminNewsPage() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCategoryInfo = (categoryId: string) => {
|
const getCategoryInfo = (categoryId: string) => categories.find(cat => cat.id === categoryId);
|
||||||
return NEWS_CATEGORIES.find(cat => cat.id === categoryId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelete = async (id: string) => {
|
const handleDelete = async (id: string) => {
|
||||||
if (!confirm('Вы уверены, что хотите удалить эту новость?')) {
|
if (!confirm('Вы уверены, что хотите удалить эту новость?')) {
|
||||||
@ -230,7 +241,7 @@ export default function AdminNewsPage() {
|
|||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
>
|
>
|
||||||
<option value="all">Все категории</option>
|
<option value="all">Все категории</option>
|
||||||
{NEWS_CATEGORIES.map((category) => (
|
{categories.map((category) => (
|
||||||
<option key={category.id} value={category.id}>
|
<option key={category.id} value={category.id}>
|
||||||
{category.name}
|
{category.name}
|
||||||
</option>
|
</option>
|
||||||
|
@ -1,68 +1,146 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { FileText, Eye, Calendar, TrendingUp, Plus } from 'lucide-react';
|
import { FileText, Eye, Calendar, TrendingUp, Plus, Users, Database, Activity } from 'lucide-react';
|
||||||
import { NEWS_DATA } from '@/lib/news-data';
|
|
||||||
|
interface DashboardStats {
|
||||||
|
totalNews: number;
|
||||||
|
publishedNews: number;
|
||||||
|
featuredNews: number;
|
||||||
|
recentNews: number;
|
||||||
|
totalUsers: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NewsItem {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
slug: string;
|
||||||
|
category: string;
|
||||||
|
featured: boolean;
|
||||||
|
published: boolean;
|
||||||
|
publishedAt: string;
|
||||||
|
views: number;
|
||||||
|
likes: number;
|
||||||
|
}
|
||||||
|
|
||||||
export default function AdminDashboard() {
|
export default function AdminDashboard() {
|
||||||
// Подсчет статистики
|
const [stats, setStats] = useState<DashboardStats | null>(null);
|
||||||
const totalNews = NEWS_DATA.length;
|
const [latestNews, setLatestNews] = useState<NewsItem[]>([]);
|
||||||
const publishedNews = NEWS_DATA.filter(news => news.published !== false).length;
|
const [loading, setLoading] = useState(true);
|
||||||
const featuredNews = NEWS_DATA.filter(news => news.featured).length;
|
|
||||||
const recentNews = NEWS_DATA.filter(news => {
|
|
||||||
const publishDate = new Date(news.publishedAt);
|
|
||||||
const weekAgo = new Date();
|
|
||||||
weekAgo.setDate(weekAgo.getDate() - 7);
|
|
||||||
return publishDate >= weekAgo;
|
|
||||||
}).length;
|
|
||||||
|
|
||||||
const stats = [
|
useEffect(() => {
|
||||||
|
loadDashboardData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadDashboardData = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
// Загружаем статистику
|
||||||
|
const healthResponse = await fetch('/api/health');
|
||||||
|
const healthData = await healthResponse.json();
|
||||||
|
|
||||||
|
// Загружаем все новости для статистики
|
||||||
|
const newsResponse = await fetch('/api/news?limit=100&published=all');
|
||||||
|
const newsData = await newsResponse.json();
|
||||||
|
|
||||||
|
if (newsData.success) {
|
||||||
|
const allNews = newsData.data.news;
|
||||||
|
const publishedNews = allNews.filter((news: NewsItem) => news.published);
|
||||||
|
const featuredNews = allNews.filter((news: NewsItem) => news.featured);
|
||||||
|
const weekAgo = new Date();
|
||||||
|
weekAgo.setDate(weekAgo.getDate() - 7);
|
||||||
|
const recentNews = allNews.filter((news: NewsItem) => {
|
||||||
|
const publishDate = new Date(news.publishedAt);
|
||||||
|
return publishDate >= weekAgo;
|
||||||
|
});
|
||||||
|
|
||||||
|
setStats({
|
||||||
|
totalNews: allNews.length,
|
||||||
|
publishedNews: publishedNews.length,
|
||||||
|
featuredNews: featuredNews.length,
|
||||||
|
recentNews: recentNews.length,
|
||||||
|
totalUsers: healthData.data?.userCount || 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// Берем последние 5 новостей
|
||||||
|
setLatestNews(allNews.slice(0, 5));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading dashboard data:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const statsCards = stats ? [
|
||||||
{
|
{
|
||||||
name: 'Всего новостей',
|
name: 'Всего новостей',
|
||||||
value: totalNews,
|
value: stats.totalNews,
|
||||||
icon: FileText,
|
icon: FileText,
|
||||||
color: 'bg-blue-500',
|
color: 'bg-gradient-to-r from-blue-500 to-blue-600',
|
||||||
textColor: 'text-blue-600'
|
textColor: 'text-blue-600',
|
||||||
|
bgColor: 'bg-blue-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Опубликовано',
|
name: 'Опубликовано',
|
||||||
value: publishedNews,
|
value: stats.publishedNews,
|
||||||
icon: Eye,
|
icon: Eye,
|
||||||
color: 'bg-green-500',
|
color: 'bg-gradient-to-r from-green-500 to-green-600',
|
||||||
textColor: 'text-green-600'
|
textColor: 'text-green-600',
|
||||||
|
bgColor: 'bg-green-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Рекомендуемые',
|
name: 'Рекомендуемые',
|
||||||
value: featuredNews,
|
value: stats.featuredNews,
|
||||||
icon: TrendingUp,
|
icon: TrendingUp,
|
||||||
color: 'bg-yellow-500',
|
color: 'bg-gradient-to-r from-yellow-500 to-yellow-600',
|
||||||
textColor: 'text-yellow-600'
|
textColor: 'text-yellow-600',
|
||||||
|
bgColor: 'bg-yellow-50'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'За неделю',
|
name: 'За неделю',
|
||||||
value: recentNews,
|
value: stats.recentNews,
|
||||||
icon: Calendar,
|
icon: Calendar,
|
||||||
color: 'bg-purple-500',
|
color: 'bg-gradient-to-r from-purple-500 to-purple-600',
|
||||||
textColor: 'text-purple-600'
|
textColor: 'text-purple-600',
|
||||||
|
bgColor: 'bg-purple-50'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Пользователи',
|
||||||
|
value: stats.totalUsers,
|
||||||
|
icon: Users,
|
||||||
|
color: 'bg-gradient-to-r from-indigo-500 to-indigo-600',
|
||||||
|
textColor: 'text-indigo-600',
|
||||||
|
bgColor: 'bg-indigo-50'
|
||||||
}
|
}
|
||||||
];
|
] : [];
|
||||||
|
|
||||||
const latestNews = NEWS_DATA
|
if (loading) {
|
||||||
.sort((a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime())
|
return (
|
||||||
.slice(0, 5);
|
<div className="min-h-screen flex items-center justify-center">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
||||||
|
<p className="text-gray-600">Загрузка данных...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between">
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Панель управления</h1>
|
<h1 className="text-3xl font-bold bg-gradient-to-r from-gray-900 to-gray-700 bg-clip-text text-transparent">
|
||||||
|
Панель управления
|
||||||
|
</h1>
|
||||||
<p className="text-gray-600 mt-2">Обзор системы управления новостями</p>
|
<p className="text-gray-600 mt-2">Обзор системы управления новостями</p>
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
href="/admin/news/create"
|
href="/admin/news/create"
|
||||||
className="mt-4 sm:mt-0 inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
className="mt-4 sm:mt-0 inline-flex items-center px-6 py-3 bg-gradient-to-r from-blue-600 to-blue-700 text-white rounded-lg hover:from-blue-700 hover:to-blue-800 transition-all duration-200 shadow-lg hover:shadow-xl transform hover:-translate-y-0.5"
|
||||||
>
|
>
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
Создать новость
|
Создать новость
|
||||||
@ -70,15 +148,15 @@ export default function AdminDashboard() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats Grid */}
|
{/* Stats Grid */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6">
|
||||||
{stats.map((stat) => (
|
{statsCards.map((stat) => (
|
||||||
<div key={stat.name} className="bg-white rounded-lg shadow-sm p-6">
|
<div key={stat.name} className={`${stat.bgColor} rounded-xl shadow-sm p-6 border border-gray-100 hover:shadow-md transition-shadow duration-200`}>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className={`p-3 rounded-lg ${stat.color} bg-opacity-10`}>
|
<div className={`p-3 rounded-lg ${stat.color} shadow-sm`}>
|
||||||
<stat.icon className={`h-6 w-6 ${stat.textColor}`} />
|
<stat.icon className="h-6 w-6 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4">
|
<div className="ml-4">
|
||||||
<p className="text-sm text-gray-600">{stat.name}</p>
|
<p className="text-sm text-gray-600 font-medium">{stat.name}</p>
|
||||||
<p className="text-2xl font-bold text-gray-900">{stat.value}</p>
|
<p className="text-2xl font-bold text-gray-900">{stat.value}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -87,13 +165,13 @@ export default function AdminDashboard() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Recent News */}
|
{/* Recent News */}
|
||||||
<div className="bg-white rounded-lg shadow-sm">
|
<div className="bg-white rounded-xl shadow-sm border border-gray-100">
|
||||||
<div className="px-6 py-4 border-b border-gray-200">
|
<div className="px-6 py-4 border-b border-gray-200">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-lg font-semibold text-gray-900">Последние новости</h2>
|
<h2 className="text-lg font-semibold text-gray-900">Последние новости</h2>
|
||||||
<Link
|
<Link
|
||||||
href="/admin/news"
|
href="/admin/news"
|
||||||
className="text-blue-600 hover:text-blue-800 text-sm font-medium"
|
className="text-blue-600 hover:text-blue-800 text-sm font-medium hover:underline"
|
||||||
>
|
>
|
||||||
Показать все
|
Показать все
|
||||||
</Link>
|
</Link>
|
||||||
@ -101,41 +179,49 @@ export default function AdminDashboard() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="divide-y divide-gray-200">
|
<div className="divide-y divide-gray-200">
|
||||||
{latestNews.map((news) => (
|
{latestNews.map((news) => (
|
||||||
<div key={news.id} className="px-6 py-4 hover:bg-gray-50">
|
<div key={news.id} className="px-6 py-4 hover:bg-gray-50 transition-colors">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<h3 className="text-sm font-medium text-gray-900 truncate">
|
<h3 className="text-sm font-medium text-gray-900 truncate max-w-md">
|
||||||
{news.title}
|
{news.title}
|
||||||
</h3>
|
</h3>
|
||||||
{news.featured && (
|
{news.featured && (
|
||||||
<span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
|
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gradient-to-r from-yellow-400 to-yellow-500 text-white">
|
||||||
Рекомендуемое
|
★ Рекомендуемое
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${
|
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
||||||
news.published !== false
|
news.published
|
||||||
? 'bg-green-100 text-green-800'
|
? 'bg-green-100 text-green-800'
|
||||||
: 'bg-gray-100 text-gray-800'
|
: 'bg-gray-100 text-gray-800'
|
||||||
}`}>
|
}`}>
|
||||||
{news.published !== false ? 'Опубликовано' : 'Черновик'}
|
{news.published ? '✓ Опубликовано' : '📝 Черновик'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-gray-500 mt-1">
|
<div className="flex items-center space-x-4 mt-2">
|
||||||
{new Date(news.publishedAt).toLocaleDateString('ru-RU')}
|
<p className="text-sm text-gray-500">
|
||||||
</p>
|
📅 {new Date(news.publishedAt).toLocaleDateString('ru-RU')}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
👁 {news.views} просмотров
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
❤️ {news.likes} лайков
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Link
|
<Link
|
||||||
href={`/admin/news/${news.id}/edit`}
|
href={`/admin/news/${news.id}/edit`}
|
||||||
className="text-blue-600 hover:text-blue-800 text-sm"
|
className="px-3 py-1 text-sm text-blue-600 hover:text-blue-800 hover:bg-blue-50 rounded-md transition-colors"
|
||||||
>
|
>
|
||||||
Редактировать
|
Редактировать
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href={`/news/${news.slug}`}
|
href={`/news/${news.slug}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-gray-600 hover:text-gray-800 text-sm"
|
className="px-3 py-1 text-sm text-gray-600 hover:text-gray-800 hover:bg-gray-50 rounded-md transition-colors"
|
||||||
>
|
>
|
||||||
Просмотр
|
Просмотр
|
||||||
</Link>
|
</Link>
|
||||||
@ -143,47 +229,108 @@ export default function AdminDashboard() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
{latestNews.length === 0 && (
|
||||||
|
<div className="px-6 py-8 text-center text-gray-500">
|
||||||
|
<FileText className="h-12 w-12 mx-auto mb-4 text-gray-300" />
|
||||||
|
<p>Новости не найдены</p>
|
||||||
|
<Link
|
||||||
|
href="/admin/news/create"
|
||||||
|
className="mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium hover:underline"
|
||||||
|
>
|
||||||
|
Создать первую новость
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Quick Actions */}
|
{/* Enhanced Quick Actions & Analytics */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
<div className="bg-white rounded-lg shadow-sm p-6">
|
<div className="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
||||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">Быстрые действия</h3>
|
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center">
|
||||||
|
<Activity className="h-5 w-5 mr-2 text-blue-600" />
|
||||||
|
Быстрые действия
|
||||||
|
</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<Link
|
<Link
|
||||||
href="/admin/news/create"
|
href="/admin/news/create"
|
||||||
className="flex items-center p-3 rounded-md hover:bg-gray-50 transition-colors"
|
className="flex items-center p-3 rounded-lg hover:bg-blue-50 transition-colors group"
|
||||||
>
|
>
|
||||||
<Plus className="h-5 w-5 text-blue-600 mr-3" />
|
<div className="p-2 bg-blue-100 rounded-lg group-hover:bg-blue-200 transition-colors">
|
||||||
<span className="text-sm font-medium text-gray-900">Создать новость</span>
|
<Plus className="h-4 w-4 text-blue-600" />
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-medium text-gray-900 ml-3">Создать новость</span>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/admin/news"
|
href="/admin/news"
|
||||||
className="flex items-center p-3 rounded-md hover:bg-gray-50 transition-colors"
|
className="flex items-center p-3 rounded-lg hover:bg-green-50 transition-colors group"
|
||||||
>
|
>
|
||||||
<FileText className="h-5 w-5 text-green-600 mr-3" />
|
<div className="p-2 bg-green-100 rounded-lg group-hover:bg-green-200 transition-colors">
|
||||||
<span className="text-sm font-medium text-gray-900">Управление новостями</span>
|
<FileText className="h-4 w-4 text-green-600" />
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-medium text-gray-900 ml-3">Управление новостями</span>
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/admin/settings"
|
||||||
|
className="flex items-center p-3 rounded-lg hover:bg-purple-50 transition-colors group"
|
||||||
|
>
|
||||||
|
<div className="p-2 bg-purple-100 rounded-lg group-hover:bg-purple-200 transition-colors">
|
||||||
|
<Database className="h-4 w-4 text-purple-600" />
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-medium text-gray-900 ml-3">Настройки</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white rounded-lg shadow-sm p-6">
|
<div className="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
||||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">Статистика по категориям</h3>
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">Статистика по категориям</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-4">
|
||||||
{['company', 'promotions', 'other'].map((category) => {
|
{[
|
||||||
const count = NEWS_DATA.filter(news => news.category === category).length;
|
{ id: 'Общие новости', name: 'Общие новости', color: 'bg-blue-500' },
|
||||||
const categoryName = category === 'company' ? 'Новости компании' :
|
{ id: 'Обследование канализации', name: 'Канализация', color: 'bg-green-500' },
|
||||||
category === 'promotions' ? 'Акции' : 'Другое';
|
{ id: 'Тепловизионная экспертиза', name: 'Тепловизор', color: 'bg-purple-500' },
|
||||||
|
{ id: 'Экспертиза при заливе', name: 'Залив', color: 'bg-red-500' },
|
||||||
|
{ id: 'Строительная экспертиза', name: 'Строительство', color: 'bg-yellow-500' }
|
||||||
|
].map((category) => {
|
||||||
|
const count = latestNews.filter(news => news.category === category.id).length;
|
||||||
return (
|
return (
|
||||||
<div key={category} className="flex items-center justify-between">
|
<div key={category.id} className="flex items-center justify-between">
|
||||||
<span className="text-sm text-gray-600">{categoryName}</span>
|
<div className="flex items-center">
|
||||||
<span className="text-sm font-medium text-gray-900">{count}</span>
|
<div className={`w-3 h-3 rounded-full ${category.color} mr-3`}></div>
|
||||||
|
<span className="text-sm text-gray-600">{category.name}</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-medium text-gray-900 bg-gray-100 px-2 py-1 rounded-full">
|
||||||
|
{count}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 rounded-xl shadow-sm p-6 border border-blue-100">
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">Система</h3>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-gray-600">База данных</span>
|
||||||
|
<span className="text-sm font-medium text-green-600 flex items-center">
|
||||||
|
<div className="w-2 h-2 bg-green-500 rounded-full mr-2"></div>
|
||||||
|
Подключена
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-gray-600">S3 хранилище</span>
|
||||||
|
<span className="text-sm font-medium text-green-600 flex items-center">
|
||||||
|
<div className="w-2 h-2 bg-green-500 rounded-full mr-2"></div>
|
||||||
|
Активно
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-gray-600">Версия</span>
|
||||||
|
<span className="text-sm font-medium text-gray-900">v1.0.0</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Save, Plus, Edit, Trash2, Settings as SettingsIcon, Palette, Globe } from 'lucide-react';
|
import { Save, Plus, Edit, Trash2, Settings as SettingsIcon, Palette, Globe, Lock, Mail } from 'lucide-react';
|
||||||
import { NEWS_CATEGORIES } from '@/lib/types';
|
import { NewsCategory, NewsCategoryInfo } from '@/lib/types';
|
||||||
|
|
||||||
export default function SettingsPage() {
|
export default function SettingsPage() {
|
||||||
const [activeTab, setActiveTab] = useState('categories');
|
const [activeTab, setActiveTab] = useState('categories');
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
const [categories, setCategories] = useState(NEWS_CATEGORIES);
|
const [categories, setCategories] = useState<NewsCategoryInfo[]>([]);
|
||||||
const [newCategory, setNewCategory] = useState({
|
const [newCategory, setNewCategory] = useState({
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
@ -38,26 +38,91 @@ export default function SettingsPage() {
|
|||||||
const tabs = [
|
const tabs = [
|
||||||
{ id: 'categories', name: 'Категории', icon: Palette },
|
{ id: 'categories', name: 'Категории', icon: Palette },
|
||||||
{ id: 'general', name: 'Общие', icon: SettingsIcon },
|
{ id: 'general', name: 'Общие', icon: SettingsIcon },
|
||||||
|
{ id: 'security', name: 'Безопасность', icon: Lock },
|
||||||
{ id: 'seo', name: 'SEO', icon: Globe }
|
{ id: 'seo', name: 'SEO', icon: Globe }
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleAddCategory = () => {
|
const [security, setSecurity] = useState({
|
||||||
|
email: 'admin@ckeproekt.ru',
|
||||||
|
username: 'admin',
|
||||||
|
currentPassword: '',
|
||||||
|
newPassword: '',
|
||||||
|
isSaving: false,
|
||||||
|
message: '' as string | null,
|
||||||
|
error: '' as string | null,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/admin/me', { cache: 'no-store' });
|
||||||
|
if (!res.ok) return;
|
||||||
|
const data = await res.json();
|
||||||
|
if (data?.user) {
|
||||||
|
setSecurity(prev => ({ ...prev, email: data.user.email, username: data.user.username }));
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// загрузка категорий из БД
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/categories', { cache: 'no-store' });
|
||||||
|
if (!res.ok) return;
|
||||||
|
const data = await res.json();
|
||||||
|
const mapped: NewsCategoryInfo[] = (data.data || []).map((c: any) => ({
|
||||||
|
id: c.slug as NewsCategory,
|
||||||
|
name: c.name,
|
||||||
|
description: c.description || '',
|
||||||
|
color: c.color || 'bg-blue-500'
|
||||||
|
}));
|
||||||
|
setCategories(mapped);
|
||||||
|
} catch {}
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleAddCategory = async () => {
|
||||||
if (!newCategory.name.trim()) return;
|
if (!newCategory.name.trim()) return;
|
||||||
|
try {
|
||||||
const category = {
|
const res = await fetch('/api/categories', {
|
||||||
id: newCategory.name.toLowerCase().replace(/\s+/g, '-'),
|
method: 'POST',
|
||||||
name: newCategory.name,
|
headers: { 'Content-Type': 'application/json' },
|
||||||
description: newCategory.description,
|
body: JSON.stringify({ name: newCategory.name, description: newCategory.description, color: newCategory.color })
|
||||||
color: newCategory.color
|
});
|
||||||
};
|
const data = await res.json();
|
||||||
|
if (!res.ok) throw new Error(data.error || 'Ошибка создания категории');
|
||||||
setCategories([...categories, category]);
|
// Преобразуем к локальному типу
|
||||||
setNewCategory({ name: '', description: '', color: 'bg-blue-500' });
|
const created: NewsCategoryInfo = {
|
||||||
|
id: data.data.slug as NewsCategory,
|
||||||
|
name: data.data.name,
|
||||||
|
description: data.data.description || '',
|
||||||
|
color: data.data.color || 'bg-blue-500'
|
||||||
|
};
|
||||||
|
setCategories(prev => [...prev, created]);
|
||||||
|
setNewCategory({ name: '', description: '', color: 'bg-blue-500' });
|
||||||
|
} catch (e) {
|
||||||
|
alert(e instanceof Error ? e.message : 'Ошибка');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteCategory = (id: string) => {
|
const handleDeleteCategory = async (slug: string) => {
|
||||||
if (confirm('Вы уверены, что хотите удалить эту категорию?')) {
|
if (!confirm('Вы уверены, что хотите удалить эту категорию?')) return;
|
||||||
setCategories(categories.filter(cat => cat.id !== id));
|
try {
|
||||||
|
// нужно найти id категории по slug через /api/categories (у нас в списке нет db id),
|
||||||
|
// поэтому запрашиваем полный список и ищем совпадение
|
||||||
|
const resList = await fetch('/api/categories');
|
||||||
|
const list = await resList.json();
|
||||||
|
const match = (list.data || []).find((c: any) => c.slug === slug);
|
||||||
|
if (!match) throw new Error('Категория не найдена');
|
||||||
|
|
||||||
|
const res = await fetch(`/api/categories/${match.id}`, { method: 'DELETE' });
|
||||||
|
const data = await res.json();
|
||||||
|
if (!res.ok) throw new Error(data.error || 'Ошибка удаления');
|
||||||
|
setCategories(prev => prev.filter(cat => cat.id !== slug));
|
||||||
|
} catch (e) {
|
||||||
|
alert(e instanceof Error ? e.message : 'Ошибка');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -65,13 +130,10 @@ export default function SettingsPage() {
|
|||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// В реальном приложении здесь будет API вызов
|
// пока сохраняем только generalSettings (к примеру локально)
|
||||||
console.log('Saving settings:', { categories, generalSettings });
|
console.log('Saving settings:', { generalSettings });
|
||||||
|
await new Promise(r => setTimeout(r, 500));
|
||||||
// Имитация задержки
|
alert('Настройки сохранены');
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
||||||
|
|
||||||
alert('Настройки сохранены успешно!');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving settings:', error);
|
console.error('Error saving settings:', error);
|
||||||
alert('Ошибка при сохранении настроек');
|
alert('Ошибка при сохранении настроек');
|
||||||
@ -130,6 +192,7 @@ export default function SettingsPage() {
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">Управление категориями</h3>
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">Управление категориями</h3>
|
||||||
|
<p className="text-sm text-gray-500 mb-4">Категории синхронизируются с базой данных</p>
|
||||||
|
|
||||||
{/* Add New Category */}
|
{/* Add New Category */}
|
||||||
<div className="bg-gray-50 rounded-lg p-4 mb-6">
|
<div className="bg-gray-50 rounded-lg p-4 mb-6">
|
||||||
@ -171,7 +234,7 @@ export default function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Categories List */}
|
{/* Categories List */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{categories.map((category) => (
|
{categories.map((category) => (
|
||||||
<div key={category.id} className="flex items-center justify-between p-4 border border-gray-200 rounded-lg">
|
<div key={category.id} className="flex items-center justify-between p-4 border border-gray-200 rounded-lg">
|
||||||
@ -183,11 +246,9 @@ export default function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<button className="p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded">
|
{/* Редактирование можно добавить при необходимости */}
|
||||||
<Edit className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDeleteCategory(category.id)}
|
onClick={() => handleDeleteCategory(category.id)}
|
||||||
className="p-2 text-red-600 hover:text-red-800 hover:bg-red-50 rounded"
|
className="p-2 text-red-600 hover:text-red-800 hover:bg-red-50 rounded"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
@ -346,6 +407,89 @@ export default function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Security Tab */}
|
||||||
|
{activeTab === 'security' && (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">Учетные данные администратора</h3>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Email</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
value={security.email}
|
||||||
|
onChange={(e) => setSecurity(prev => ({ ...prev, email: e.target.value }))}
|
||||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
placeholder="admin@ckeproekt.ru"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Логин</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={security.username}
|
||||||
|
onChange={(e) => setSecurity(prev => ({ ...prev, username: e.target.value }))}
|
||||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
placeholder="admin"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Текущий пароль</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={security.currentPassword}
|
||||||
|
onChange={(e) => setSecurity(prev => ({ ...prev, currentPassword: e.target.value }))}
|
||||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
placeholder="Введите текущий пароль"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Новый пароль</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={security.newPassword}
|
||||||
|
onChange={(e) => setSecurity(prev => ({ ...prev, newPassword: e.target.value }))}
|
||||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
placeholder="Оставьте пустым, чтобы не менять"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4">
|
||||||
|
<button
|
||||||
|
disabled={security.isSaving}
|
||||||
|
onClick={async () => {
|
||||||
|
setSecurity(prev => ({ ...prev, isSaving: true, message: null, error: null }));
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/admin/credentials', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: security.email,
|
||||||
|
username: security.username,
|
||||||
|
currentPassword: security.currentPassword,
|
||||||
|
newPassword: security.newPassword || undefined,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (!res.ok) throw new Error(data.error || 'Ошибка сохранения');
|
||||||
|
setSecurity(prev => ({ ...prev, message: 'Изменения сохранены', currentPassword: '', newPassword: '' }));
|
||||||
|
} catch (e: any) {
|
||||||
|
setSecurity(prev => ({ ...prev, error: e.message || 'Ошибка' }));
|
||||||
|
} finally {
|
||||||
|
setSecurity(prev => ({ ...prev, isSaving: false }));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{security.isSaving ? 'Сохранение...' : 'Сохранить учетные данные'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{security.message && <p className="text-green-600 text-sm mt-2">{security.message}</p>}
|
||||||
|
{security.error && <p className="text-red-600 text-sm mt-2">{security.error}</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
26
app/admin/test-s3/page.tsx
Normal file
26
app/admin/test-s3/page.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import S3Status from '../components/S3Status';
|
||||||
|
|
||||||
|
export default function TestS3Page() {
|
||||||
|
return (
|
||||||
|
<div className="p-8">
|
||||||
|
<h1 className="text-2xl font-bold mb-8">Тест S3 подключения</h1>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-lg shadow p-6">
|
||||||
|
<h2 className="text-lg font-semibold mb-4">Статус S3:</h2>
|
||||||
|
<S3Status />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 bg-gray-100 rounded-lg p-6">
|
||||||
|
<h2 className="text-lg font-semibold mb-4">Инструкции:</h2>
|
||||||
|
<ol className="list-decimal list-inside space-y-2 text-gray-700">
|
||||||
|
<li>Откройте консоль разработчика (F12)</li>
|
||||||
|
<li>Посмотрите на логи S3Status</li>
|
||||||
|
<li>Если есть ошибка, нажмите кнопку "Повторить"</li>
|
||||||
|
<li>Проверьте сетевые запросы в вкладке Network</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
54
app/api/admin/credentials/route.ts
Normal file
54
app/api/admin/credentials/route.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { getAuthContext, hashPassword, verifyPassword } from '@/lib/auth';
|
||||||
|
import prisma from '@/lib/database';
|
||||||
|
|
||||||
|
export async function PUT(request: NextRequest) {
|
||||||
|
const context = await getAuthContext(request);
|
||||||
|
if (!context.user || context.user.role !== 'ADMIN') {
|
||||||
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { email, username, currentPassword, newPassword } = await request.json();
|
||||||
|
|
||||||
|
if (!currentPassword) {
|
||||||
|
return NextResponse.json({ error: 'Текущий пароль обязателен' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await prisma.user.findUnique({ where: { id: context.user.id } });
|
||||||
|
if (!user) {
|
||||||
|
return NextResponse.json({ error: 'Пользователь не найден' }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка текущего пароля
|
||||||
|
const isValid = await verifyPassword(currentPassword, user.password);
|
||||||
|
if (!isValid) {
|
||||||
|
return NextResponse.json({ error: 'Неверный текущий пароль' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: any = {};
|
||||||
|
if (email && email !== user.email) data.email = email;
|
||||||
|
if (username && username !== user.username) data.username = username;
|
||||||
|
if (newPassword && newPassword.length >= 6) {
|
||||||
|
data.password = await hashPassword(newPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(data).length === 0) {
|
||||||
|
return NextResponse.json({ success: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = await prisma.user.update({
|
||||||
|
where: { id: user.id },
|
||||||
|
data,
|
||||||
|
select: { id: true, email: true, username: true, role: true, name: true, avatar: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json({ user: updated });
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.code === 'P2002') {
|
||||||
|
return NextResponse.json({ error: 'Email или логин уже заняты' }, { status: 409 });
|
||||||
|
}
|
||||||
|
return NextResponse.json({ error: 'Внутренняя ошибка сервера' }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
51
app/api/admin/login/route.ts
Normal file
51
app/api/admin/login/route.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import prisma from '@/lib/database';
|
||||||
|
import { verifyPassword, generateToken } from '@/lib/auth';
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { identifier, password } = await request.json();
|
||||||
|
|
||||||
|
if (!identifier || !password) {
|
||||||
|
return NextResponse.json({ error: 'Логин/Email и пароль обязательны' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await prisma.user.findFirst({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{ email: identifier },
|
||||||
|
{ username: identifier }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return NextResponse.json({ error: 'Неверные учетные данные' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.role !== 'ADMIN') {
|
||||||
|
return NextResponse.json({ error: 'Доступ запрещен' }, { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValid = await verifyPassword(password, user.password);
|
||||||
|
if (!isValid) {
|
||||||
|
return NextResponse.json({ error: 'Неверные учетные данные' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = generateToken(user.id);
|
||||||
|
const { password: _pwd, ...safeUser } = user as any;
|
||||||
|
|
||||||
|
const response = NextResponse.json({ user: safeUser });
|
||||||
|
response.cookies.set('auth-token', token, {
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: 'lax',
|
||||||
|
secure: process.env.NODE_ENV === 'production',
|
||||||
|
path: '/',
|
||||||
|
maxAge: 60 * 60 * 24 * 7, // 7 дней
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json({ error: 'Внутренняя ошибка сервера' }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
14
app/api/admin/logout/route.ts
Normal file
14
app/api/admin/logout/route.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
|
||||||
|
export async function POST() {
|
||||||
|
const response = NextResponse.json({ success: true });
|
||||||
|
response.cookies.set('auth-token', '', {
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: 'lax',
|
||||||
|
secure: process.env.NODE_ENV === 'production',
|
||||||
|
path: '/',
|
||||||
|
maxAge: 0,
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
11
app/api/admin/me/route.ts
Normal file
11
app/api/admin/me/route.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { getAuthContext } from '@/lib/auth';
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
const context = await getAuthContext(request);
|
||||||
|
if (!context.user || context.user.role !== 'ADMIN') {
|
||||||
|
return NextResponse.json({ user: null }, { status: 401 });
|
||||||
|
}
|
||||||
|
return NextResponse.json({ user: context.user });
|
||||||
|
}
|
||||||
|
|
51
app/api/categories/[id]/route.ts
Normal file
51
app/api/categories/[id]/route.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import prisma from '@/lib/database';
|
||||||
|
|
||||||
|
export async function PUT(
|
||||||
|
request: Request,
|
||||||
|
{ params }: { params: { id: string } }
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const body = await request.json();
|
||||||
|
const { name, description, color, slug } = body;
|
||||||
|
const id = params.id;
|
||||||
|
|
||||||
|
const updated = await prisma.category.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
...(name ? { name } : {}),
|
||||||
|
...(description !== undefined ? { description } : {}),
|
||||||
|
...(color ? { color } : {}),
|
||||||
|
...(slug ? { slug } : {}),
|
||||||
|
},
|
||||||
|
select: { id: true, name: true, slug: true, description: true, color: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json({ success: true, data: updated });
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.code === 'P2025') {
|
||||||
|
return NextResponse.json({ success: false, error: 'Категория не найдена' }, { status: 404 });
|
||||||
|
}
|
||||||
|
if (error.code === 'P2002') {
|
||||||
|
return NextResponse.json({ success: false, error: 'Имя или слаг уже используются' }, { status: 409 });
|
||||||
|
}
|
||||||
|
return NextResponse.json({ success: false, error: 'Не удалось обновить категорию' }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function DELETE(
|
||||||
|
_request: Request,
|
||||||
|
{ params }: { params: { id: string } }
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const id = params.id;
|
||||||
|
await prisma.category.delete({ where: { id } });
|
||||||
|
return NextResponse.json({ success: true });
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.code === 'P2025') {
|
||||||
|
return NextResponse.json({ success: false, error: 'Категория не найдена' }, { status: 404 });
|
||||||
|
}
|
||||||
|
return NextResponse.json({ success: false, error: 'Не удалось удалить категорию' }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
56
app/api/categories/route.ts
Normal file
56
app/api/categories/route.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import prisma from '@/lib/database';
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
const categories = await prisma.category.findMany({
|
||||||
|
orderBy: { name: 'asc' },
|
||||||
|
select: { id: true, name: true, slug: true, description: true, color: true }
|
||||||
|
});
|
||||||
|
return NextResponse.json({ success: true, data: categories });
|
||||||
|
} catch {
|
||||||
|
return NextResponse.json({ success: false, error: 'Не удалось получить категории' }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(request: Request) {
|
||||||
|
try {
|
||||||
|
const body = await request.json();
|
||||||
|
const name: string = body.name;
|
||||||
|
const description: string | undefined = body.description;
|
||||||
|
const color: string | undefined = body.color;
|
||||||
|
let slug: string | undefined = body.slug;
|
||||||
|
|
||||||
|
if (!name || !name.trim()) {
|
||||||
|
return NextResponse.json({ success: false, error: 'Название обязательно' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!slug) {
|
||||||
|
slug = name
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^a-z0-9а-яё\s-]/g, '')
|
||||||
|
.replace(/[\s_]+/g, '-')
|
||||||
|
.replace(/-+/g, '-')
|
||||||
|
.replace(/^-|-$/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
const created = await prisma.category.create({
|
||||||
|
data: {
|
||||||
|
name: name.trim(),
|
||||||
|
slug,
|
||||||
|
description,
|
||||||
|
color: color || 'bg-blue-500'
|
||||||
|
},
|
||||||
|
select: { id: true, name: true, slug: true, description: true, color: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json({ success: true, data: created }, { status: 201 });
|
||||||
|
} catch (e: unknown) {
|
||||||
|
const code = typeof e === 'object' && e !== null && 'code' in e ? (e as { code?: string }).code : undefined;
|
||||||
|
if (code === 'P2002') {
|
||||||
|
return NextResponse.json({ success: false, error: 'Категория с таким именем или слагом уже существует' }, { status: 409 });
|
||||||
|
}
|
||||||
|
return NextResponse.json({ success: false, error: 'Не удалось создать категорию' }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
36
app/api/health/route.ts
Normal file
36
app/api/health/route.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
// Проверяем подключение к базе данных
|
||||||
|
await prisma.$connect();
|
||||||
|
|
||||||
|
// Проверяем, что можем выполнить запрос
|
||||||
|
const newsCount = await prisma.news.count();
|
||||||
|
const userCount = await prisma.user.count();
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
status: 'healthy',
|
||||||
|
database: 'connected',
|
||||||
|
data: {
|
||||||
|
newsCount,
|
||||||
|
userCount,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Health check failed:', error);
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
status: 'unhealthy',
|
||||||
|
database: 'disconnected',
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
}, { status: 500 });
|
||||||
|
} finally {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
}
|
@ -5,11 +5,12 @@ const prisma = new PrismaClient();
|
|||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
{ params }: { params: { id: string } }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
|
const resolvedParams = await params;
|
||||||
const news = await prisma.news.findUnique({
|
const news = await prisma.news.findUnique({
|
||||||
where: { id: params.id },
|
where: { id: resolvedParams.id },
|
||||||
include: { author: true }
|
include: { author: true }
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ export async function GET(
|
|||||||
|
|
||||||
// Увеличиваем счетчик просмотров
|
// Увеличиваем счетчик просмотров
|
||||||
await prisma.news.update({
|
await prisma.news.update({
|
||||||
where: { id: params.id },
|
where: { id: resolvedParams.id },
|
||||||
data: { views: { increment: 1 } }
|
data: { views: { increment: 1 } }
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -47,9 +48,10 @@ export async function GET(
|
|||||||
|
|
||||||
export async function PUT(
|
export async function PUT(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
{ params }: { params: { id: string } }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
|
const resolvedParams = await params;
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
|
|
||||||
// Здесь должна быть проверка авторизации
|
// Здесь должна быть проверка авторизации
|
||||||
@ -78,7 +80,7 @@ export async function PUT(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const news = await prisma.news.update({
|
const news = await prisma.news.update({
|
||||||
where: { id: params.id },
|
where: { id: resolvedParams.id },
|
||||||
data: updateData,
|
data: updateData,
|
||||||
include: { author: true }
|
include: { author: true }
|
||||||
});
|
});
|
||||||
@ -104,9 +106,10 @@ export async function PUT(
|
|||||||
|
|
||||||
export async function DELETE(
|
export async function DELETE(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
{ params }: { params: { id: string } }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
|
const resolvedParams = await params;
|
||||||
// Здесь должна быть проверка авторизации
|
// Здесь должна быть проверка авторизации
|
||||||
// const session = await getServerSession(authOptions);
|
// const session = await getServerSession(authOptions);
|
||||||
// if (!session) {
|
// if (!session) {
|
||||||
@ -114,7 +117,7 @@ export async function DELETE(
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
await prisma.news.delete({
|
await prisma.news.delete({
|
||||||
where: { id: params.id }
|
where: { id: resolvedParams.id }
|
||||||
});
|
});
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
|
@ -13,7 +13,8 @@ export async function GET(request: NextRequest) {
|
|||||||
const search = searchParams.get('search');
|
const search = searchParams.get('search');
|
||||||
const slug = searchParams.get('slug');
|
const slug = searchParams.get('slug');
|
||||||
const featured = searchParams.get('featured') === 'true';
|
const featured = searchParams.get('featured') === 'true';
|
||||||
const published = searchParams.get('published') !== 'false';
|
const publishedParam = searchParams.get('published');
|
||||||
|
const published = publishedParam === 'all' ? undefined : publishedParam !== 'false';
|
||||||
const sortBy = searchParams.get('sortBy') || 'publishedAt';
|
const sortBy = searchParams.get('sortBy') || 'publishedAt';
|
||||||
const sortOrder = searchParams.get('sortOrder') || 'desc';
|
const sortOrder = searchParams.get('sortOrder') || 'desc';
|
||||||
|
|
||||||
|
82
app/api/test-s3/route.ts
Normal file
82
app/api/test-s3/route.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const s3Client = new S3Client({
|
||||||
|
endpoint: process.env.S3_ENDPOINT,
|
||||||
|
region: process.env.S3_REGION || 'ru-1',
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: process.env.S3_ACCESS_KEY_ID!,
|
||||||
|
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
|
||||||
|
},
|
||||||
|
forcePathStyle: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const bucketName = process.env.S3_BUCKET_NAME!;
|
||||||
|
const testKey = `test/health-check-${Date.now()}.txt`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Шаг 1: Проверяем переменные окружения
|
||||||
|
if (!process.env.S3_ENDPOINT) {
|
||||||
|
throw new Error('S3_ENDPOINT не настроен');
|
||||||
|
}
|
||||||
|
if (!process.env.S3_BUCKET_NAME) {
|
||||||
|
throw new Error('S3_BUCKET_NAME не настроен');
|
||||||
|
}
|
||||||
|
if (!process.env.S3_ACCESS_KEY_ID) {
|
||||||
|
throw new Error('S3_ACCESS_KEY_ID не настроен');
|
||||||
|
}
|
||||||
|
if (!process.env.S3_SECRET_ACCESS_KEY) {
|
||||||
|
throw new Error('S3_SECRET_ACCESS_KEY не настроен');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Шаг 2: Пробуем загрузить тестовый файл
|
||||||
|
const testData = Buffer.from('S3 health check test');
|
||||||
|
const putCommand = new PutObjectCommand({
|
||||||
|
Bucket: bucketName,
|
||||||
|
Key: testKey,
|
||||||
|
Body: testData,
|
||||||
|
ContentType: 'text/plain',
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploadResult = await s3Client.send(putCommand);
|
||||||
|
|
||||||
|
// Шаг 3: Пробуем удалить тестовый файл
|
||||||
|
const deleteCommand = new DeleteObjectCommand({
|
||||||
|
Bucket: bucketName,
|
||||||
|
Key: testKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
await s3Client.send(deleteCommand);
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: 'S3 подключение работает нормально',
|
||||||
|
details: {
|
||||||
|
endpoint: process.env.S3_ENDPOINT,
|
||||||
|
bucket: bucketName,
|
||||||
|
region: process.env.S3_REGION,
|
||||||
|
uploadResult: {
|
||||||
|
httpStatusCode: uploadResult.$metadata.httpStatusCode,
|
||||||
|
etag: uploadResult.ETag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('S3 Health Check Error:', error);
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Неизвестная ошибка',
|
||||||
|
details: {
|
||||||
|
endpoint: process.env.S3_ENDPOINT,
|
||||||
|
bucket: bucketName,
|
||||||
|
region: process.env.S3_REGION,
|
||||||
|
errorName: error instanceof Error ? error.name : 'Unknown',
|
||||||
|
errorCode: (error as any)?.code,
|
||||||
|
errorStatusCode: (error as any)?.$metadata?.httpStatusCode,
|
||||||
|
}
|
||||||
|
}, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
122
app/api/upload/route.ts
Normal file
122
app/api/upload/route.ts
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { uploadFileToS3, deleteFileFromS3, extractKeyFromUrl } from '@/lib/s3';
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
console.log('API Upload: Получен запрос POST');
|
||||||
|
try {
|
||||||
|
const formData = await request.formData();
|
||||||
|
const file = formData.get('file') as File;
|
||||||
|
const folder = formData.get('folder') as string || 'uploads';
|
||||||
|
const oldUrl = formData.get('oldUrl') as string;
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Файл не найден' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем размер файла (максимум 10MB)
|
||||||
|
const maxSize = 10 * 1024 * 1024; // 10MB
|
||||||
|
if (file.size > maxSize) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Файл слишком большой. Максимальный размер: 10MB' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем тип файла
|
||||||
|
const allowedTypes = [
|
||||||
|
'image/jpeg',
|
||||||
|
'image/png',
|
||||||
|
'image/gif',
|
||||||
|
'image/webp',
|
||||||
|
'image/svg+xml',
|
||||||
|
'application/pdf',
|
||||||
|
'application/msword',
|
||||||
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||||
|
'text/plain', // Для тестирования S3Status
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!allowedTypes.includes(file.type)) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Неподдерживаемый тип файла' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если есть старый файл, удаляем его
|
||||||
|
if (oldUrl) {
|
||||||
|
const oldKey = extractKeyFromUrl(oldUrl);
|
||||||
|
if (oldKey) {
|
||||||
|
try {
|
||||||
|
await deleteFileFromS3(oldKey);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при удалении старого файла:', error);
|
||||||
|
// Не прерываем процесс, если не удалось удалить старый файл
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Конвертируем файл в Buffer
|
||||||
|
const bytes = await file.arrayBuffer();
|
||||||
|
const buffer = Buffer.from(bytes);
|
||||||
|
|
||||||
|
// Загружаем файл в S3
|
||||||
|
const result = await uploadFileToS3(
|
||||||
|
buffer,
|
||||||
|
file.type,
|
||||||
|
folder,
|
||||||
|
file.name
|
||||||
|
);
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
data: result,
|
||||||
|
message: 'Файл успешно загружен'
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при загрузке файла:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Ошибка при загрузке файла' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function DELETE(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { searchParams } = new URL(request.url);
|
||||||
|
const url = searchParams.get('url');
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'URL файла не указан' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = extractKeyFromUrl(url);
|
||||||
|
if (!key) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Некорректный URL файла' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await deleteFileFromS3(key);
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Файл успешно удален'
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при удалении файла:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Ошибка при удалении файла' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -45,7 +45,7 @@ const About = () => {
|
|||||||
opacity: 1,
|
opacity: 1,
|
||||||
transition: {
|
transition: {
|
||||||
duration: 0.8,
|
duration: 0.8,
|
||||||
ease: [0.16, 1, 0.3, 1],
|
ease: "easeOut",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -21,11 +21,13 @@ export default function NewsBlock({
|
|||||||
const [news, setNews] = useState<any[]>([]);
|
const [news, setNews] = useState<any[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [totalNews, setTotalNews] = useState(0);
|
const [totalNews, setTotalNews] = useState(0);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadNews = async () => {
|
const loadNews = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append('page', '1');
|
params.append('page', '1');
|
||||||
@ -41,16 +43,22 @@ export default function NewsBlock({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`/api/news?${params}`);
|
const response = await fetch(`/api/news?${params}`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
setNews(data.data.news);
|
setNews(data.data.news);
|
||||||
setTotalNews(data.data.pagination.total);
|
setTotalNews(data.data.pagination.total);
|
||||||
} else {
|
} else {
|
||||||
console.error('Error loading news:', data.error);
|
throw new Error(data.error || 'Failed to load news');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading news:', error);
|
console.error('Error loading news:', error);
|
||||||
|
setError(error instanceof Error ? error.message : 'Failed to load news');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@ -86,8 +94,40 @@ export default function NewsBlock({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<section className="py-20 bg-gray-50">
|
||||||
|
<div className="container mx-auto px-4">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-red-500 mb-4">
|
||||||
|
<svg className="h-12 w-12 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<p className="text-lg font-semibold">Ошибка при загрузке новостей</p>
|
||||||
|
<p className="text-sm text-gray-600 mt-2">{error}</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
||||||
|
>
|
||||||
|
Обновить страницу
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (displayNews.length === 0) {
|
if (displayNews.length === 0) {
|
||||||
return null;
|
return (
|
||||||
|
<section className="py-20 bg-gray-50">
|
||||||
|
<div className="container mx-auto px-4">
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="text-gray-600">Новости не найдены</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
501
app/news/NewsPageComponent.tsx
Normal file
501
app/news/NewsPageComponent.tsx
Normal file
@ -0,0 +1,501 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useSearchParams, useRouter, usePathname } from 'next/navigation';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { NewsItem } from '@/lib/types';
|
||||||
|
import { Search, Eye, ArrowRight } from 'lucide-react';
|
||||||
|
import Header from '@/app/components/Header';
|
||||||
|
import Footer from '@/app/components/Footer';
|
||||||
|
|
||||||
|
const ITEMS_PER_PAGE = 9;
|
||||||
|
|
||||||
|
type SortOption = 'newest' | 'oldest' | 'alphabetical' | 'featured';
|
||||||
|
|
||||||
|
export default function NewsPageComponent() {
|
||||||
|
// Устанавливаем заголовок страницы
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = 'Новости | CKE Project';
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
const [selectedCity, setSelectedCity] = useState<'Москва' | 'Чебоксары'>('Москва');
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState('all');
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const [sortBy, setSortBy] = useState<SortOption>('newest');
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
|
const handleCityChange = (city: 'Москва' | 'Чебоксары') => {
|
||||||
|
setSelectedCity(city);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Синхронизация с URL параметрами
|
||||||
|
useEffect(() => {
|
||||||
|
const category = searchParams.get('category') || 'all';
|
||||||
|
const search = searchParams.get('search') || '';
|
||||||
|
const sort = (searchParams.get('sort') as SortOption) || 'newest';
|
||||||
|
const page = parseInt(searchParams.get('page') || '1');
|
||||||
|
|
||||||
|
setSelectedCategory(category);
|
||||||
|
setSearchQuery(search);
|
||||||
|
setSortBy(sort);
|
||||||
|
setCurrentPage(page);
|
||||||
|
}, [searchParams]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (selectedCategory !== 'all') params.set('category', selectedCategory);
|
||||||
|
if (searchQuery) params.set('search', searchQuery);
|
||||||
|
if (sortBy !== 'newest') params.set('sort', sortBy);
|
||||||
|
if (currentPage !== 1) params.set('page', currentPage.toString());
|
||||||
|
|
||||||
|
const newUrl = `${pathname}?${params.toString()}`;
|
||||||
|
router.replace(newUrl);
|
||||||
|
}, [selectedCategory, searchQuery, sortBy, currentPage, pathname, router]);
|
||||||
|
|
||||||
|
const [news, setNews] = useState<NewsItem[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [totalNews, setTotalNews] = useState(0);
|
||||||
|
const [categories, setCategories] = useState<{ id: string; name: string; color: string }[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/categories', { cache: 'no-store' });
|
||||||
|
const data = await res.json();
|
||||||
|
if (res.ok && data?.data?.length) {
|
||||||
|
setCategories(data.data.map((c: any) => ({ id: c.slug, name: c.name, color: c.color || 'bg-gray-500' })));
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
})();
|
||||||
|
const loadNews = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('page', currentPage.toString());
|
||||||
|
params.append('limit', ITEMS_PER_PAGE.toString());
|
||||||
|
|
||||||
|
if (selectedCategory !== 'all') {
|
||||||
|
params.append('category', selectedCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchQuery.trim()) {
|
||||||
|
params.append('search', searchQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Конвертируем sortBy в параметры API
|
||||||
|
switch (sortBy) {
|
||||||
|
case 'newest':
|
||||||
|
params.append('sortBy', 'publishedAt');
|
||||||
|
params.append('sortOrder', 'desc');
|
||||||
|
break;
|
||||||
|
case 'oldest':
|
||||||
|
params.append('sortBy', 'publishedAt');
|
||||||
|
params.append('sortOrder', 'asc');
|
||||||
|
break;
|
||||||
|
case 'alphabetical':
|
||||||
|
params.append('sortBy', 'title');
|
||||||
|
params.append('sortOrder', 'asc');
|
||||||
|
break;
|
||||||
|
case 'featured':
|
||||||
|
params.append('featured', 'true');
|
||||||
|
params.append('sortBy', 'publishedAt');
|
||||||
|
params.append('sortOrder', 'desc');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`/api/news?${params}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
setNews(data.data.news);
|
||||||
|
setTotalNews(data.data.pagination.total);
|
||||||
|
} else {
|
||||||
|
console.error('Error loading news:', data.error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading news:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadNews();
|
||||||
|
}, [selectedCategory, searchQuery, sortBy, currentPage]);
|
||||||
|
|
||||||
|
const formatDate = (dateString: string) => {
|
||||||
|
return new Date(dateString).toLocaleDateString('ru-RU', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCategoryInfo = (categoryId: string) => categories.find(cat => cat.id === categoryId);
|
||||||
|
|
||||||
|
const handleCategoryChange = (category: string) => {
|
||||||
|
setSelectedCategory(category);
|
||||||
|
setCurrentPage(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchChange = (query: string) => {
|
||||||
|
setSearchQuery(query);
|
||||||
|
setCurrentPage(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSortChange = (sort: SortOption) => {
|
||||||
|
setSortBy(sort);
|
||||||
|
setCurrentPage(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageChange = (page: number) => {
|
||||||
|
setCurrentPage(page);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
};
|
||||||
|
|
||||||
|
const featuredNews = news.find(item => item.featured) || news[0];
|
||||||
|
const otherNews = news.filter(item => item.id !== featuredNews?.id);
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(totalNews / ITEMS_PER_PAGE);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-white flex flex-col">
|
||||||
|
<Header selectedCity={selectedCity} onCityChange={handleCityChange} />
|
||||||
|
<main className="flex-1 flex items-center justify-center pt-20">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="animate-spin rounded-full h-16 w-16 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
||||||
|
<p className="text-gray-600 text-lg">Загрузка новостей...</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<Footer selectedCity={selectedCity} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-white flex flex-col">
|
||||||
|
<Header selectedCity={selectedCity} onCityChange={handleCityChange} />
|
||||||
|
|
||||||
|
<main className="flex-1 pt-20">
|
||||||
|
{/* Хлебные крошки */}
|
||||||
|
<div className="bg-gray-50 py-4">
|
||||||
|
<div className="container mx-auto px-4">
|
||||||
|
<div className="flex items-center space-x-2 text-sm text-gray-600">
|
||||||
|
<Link href="/" className="hover:text-blue-600 transition-colors">
|
||||||
|
Главная
|
||||||
|
</Link>
|
||||||
|
<span>/</span>
|
||||||
|
<span className="text-gray-900">Новости</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Заголовок страницы */}
|
||||||
|
<section className="py-16 bg-white">
|
||||||
|
<div className="container mx-auto px-4">
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-6">
|
||||||
|
Новости и События
|
||||||
|
</h1>
|
||||||
|
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||||
|
Следите за последними событиями, достижениями и обновлениями нашей компании
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Панель фильтров */}
|
||||||
|
<section className="py-8 bg-gray-50">
|
||||||
|
<div className="container mx-auto px-4">
|
||||||
|
<div className="bg-white rounded-2xl shadow-lg p-6 border border-gray-100">
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Поиск */}
|
||||||
|
<div className="relative">
|
||||||
|
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Поиск по заголовку, описанию или содержимому..."
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => handleSearchChange(e.target.value)}
|
||||||
|
className="w-full pl-12 pr-4 py-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-900 placeholder-gray-500 transition-all duration-200"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Фильтры */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
|
{/* Категории */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-700 font-semibold mb-3 text-sm">
|
||||||
|
Категория
|
||||||
|
</label>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => handleCategoryChange('all')}
|
||||||
|
className={`px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 ${
|
||||||
|
selectedCategory === 'all'
|
||||||
|
? 'bg-blue-600 text-white shadow-md'
|
||||||
|
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Все
|
||||||
|
</button>
|
||||||
|
{categories.map((category) => (
|
||||||
|
<button
|
||||||
|
key={category.id}
|
||||||
|
onClick={() => handleCategoryChange(category.id)}
|
||||||
|
className={`px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 ${
|
||||||
|
selectedCategory === category.id
|
||||||
|
? 'bg-blue-600 text-white shadow-md'
|
||||||
|
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{category.name}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Сортировка */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-700 font-semibold mb-3 text-sm">
|
||||||
|
Сортировка
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={sortBy}
|
||||||
|
onChange={(e) => handleSortChange(e.target.value as SortOption)}
|
||||||
|
className="w-full px-4 py-3 border border-gray-200 rounded-xl text-gray-900 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200"
|
||||||
|
>
|
||||||
|
<option value="newest">Сначала новые</option>
|
||||||
|
<option value="oldest">Сначала старые</option>
|
||||||
|
<option value="alphabetical">По алфавиту</option>
|
||||||
|
<option value="featured">Важные первыми</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Статистика */}
|
||||||
|
<div className="flex items-center justify-center lg:justify-end">
|
||||||
|
<div className="bg-gray-100 rounded-xl px-6 py-4 border border-gray-200">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-2xl font-bold text-gray-900">{totalNews}</div>
|
||||||
|
<div className="text-gray-600 text-sm">найдено</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Главная новость */}
|
||||||
|
{currentPage === 1 && featuredNews && (
|
||||||
|
<section className="py-16 bg-white">
|
||||||
|
<div className="container mx-auto px-4">
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<h2 className="text-3xl font-bold text-gray-900 mb-4">
|
||||||
|
Главная новость
|
||||||
|
</h2>
|
||||||
|
<div className="w-24 h-1 bg-blue-600 mx-auto rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<article className="bg-white rounded-3xl shadow-2xl overflow-hidden border border-gray-100 hover:shadow-3xl transition-shadow duration-500">
|
||||||
|
<div className="lg:flex">
|
||||||
|
<div className="lg:w-1/2 relative h-64 lg:h-80">
|
||||||
|
<Image
|
||||||
|
src={featuredNews.imageUrl || '/images/office.jpg'}
|
||||||
|
alt={featuredNews.title}
|
||||||
|
fill
|
||||||
|
className="object-cover"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent"></div>
|
||||||
|
<div className="absolute top-6 right-6 px-4 py-2 bg-gradient-to-r from-yellow-400 to-orange-500 text-white text-sm font-semibold rounded-full shadow-lg">
|
||||||
|
Важное
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="lg:w-1/2 p-8 lg:p-12">
|
||||||
|
<div className="text-sm text-blue-600 font-semibold mb-4 uppercase tracking-wide">
|
||||||
|
{formatDate(featuredNews.publishedAt)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-6 leading-tight">
|
||||||
|
{featuredNews.title}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p className="text-lg text-gray-600 mb-8 leading-relaxed">
|
||||||
|
{featuredNews.summary}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href={`/news/${featuredNews.slug}`}
|
||||||
|
className="inline-flex items-center px-8 py-4 bg-gradient-to-r from-blue-600 to-indigo-600 text-white font-semibold rounded-xl hover:from-blue-700 hover:to-indigo-700 transition-all duration-300 transform hover:scale-105 shadow-lg hover:shadow-xl"
|
||||||
|
>
|
||||||
|
Читать полностью
|
||||||
|
<ArrowRight className="w-5 h-5 ml-3" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Сетка новостей */}
|
||||||
|
<section className="py-16 bg-gray-50">
|
||||||
|
<div className="container mx-auto px-4">
|
||||||
|
{news.length > 0 ? (
|
||||||
|
<>
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<h2 className="text-3xl font-bold text-gray-900 mb-4">
|
||||||
|
{currentPage === 1 && featuredNews ? 'Другие новости' : 'Все новости'}
|
||||||
|
</h2>
|
||||||
|
<div className="w-24 h-1 bg-blue-600 mx-auto rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-8 mb-16">
|
||||||
|
{(currentPage === 1 && featuredNews ? otherNews : news).map((newsItem) => {
|
||||||
|
const categoryInfo = getCategoryInfo(newsItem.category);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
key={newsItem.id}
|
||||||
|
className="group bg-white rounded-2xl shadow-lg overflow-hidden hover:shadow-2xl transition-all duration-500 transform hover:-translate-y-2 border border-gray-100"
|
||||||
|
>
|
||||||
|
<div className="relative h-56 overflow-hidden">
|
||||||
|
<Image
|
||||||
|
src={newsItem.imageUrl || '/images/office.jpg'}
|
||||||
|
alt={newsItem.title}
|
||||||
|
fill
|
||||||
|
className="object-cover group-hover:scale-110 transition-transform duration-700"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-black/30 to-transparent"></div>
|
||||||
|
|
||||||
|
{/* Категория */}
|
||||||
|
{categoryInfo && (
|
||||||
|
<div className="absolute top-4 left-4">
|
||||||
|
<span className={`px-3 py-1 rounded-full text-xs font-semibold text-white ${categoryInfo.color} shadow-lg`}>
|
||||||
|
{categoryInfo.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Дата */}
|
||||||
|
<div className="absolute bottom-4 right-4">
|
||||||
|
<span className="px-3 py-1 bg-black/50 text-white text-xs rounded-full">
|
||||||
|
{formatDate(newsItem.publishedAt)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-6">
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-3 line-clamp-2 group-hover:text-blue-600 transition-colors duration-300">
|
||||||
|
{newsItem.title}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p className="text-gray-600 mb-6 line-clamp-3 leading-relaxed">
|
||||||
|
{newsItem.summary}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href={`/news/${newsItem.slug}`}
|
||||||
|
className="inline-flex items-center text-blue-600 hover:text-blue-800 font-semibold transition-colors duration-300"
|
||||||
|
>
|
||||||
|
Читать далее
|
||||||
|
<ArrowRight className="w-4 h-4 ml-2" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-20">
|
||||||
|
<div className="bg-white rounded-3xl shadow-xl p-12 max-w-md mx-auto border border-gray-100">
|
||||||
|
<div className="w-20 h-20 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||||
|
<Search className="w-10 h-10 text-gray-400" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900 mb-4">
|
||||||
|
Новостей не найдено
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-600 text-lg mb-8">
|
||||||
|
Попробуйте изменить фильтры или поисковый запрос
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedCategory('all');
|
||||||
|
setSearchQuery('');
|
||||||
|
setSortBy('newest');
|
||||||
|
setCurrentPage(1);
|
||||||
|
}}
|
||||||
|
className="px-8 py-4 bg-blue-600 text-white font-semibold rounded-xl hover:bg-blue-700 transition-colors duration-300"
|
||||||
|
>
|
||||||
|
Сбросить фильтры
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Пагинация */}
|
||||||
|
{totalPages > 1 && (
|
||||||
|
<div className="flex justify-center items-center space-x-2 mt-16">
|
||||||
|
<button
|
||||||
|
onClick={() => handlePageChange(currentPage - 1)}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
className="px-6 py-3 bg-white text-gray-700 rounded-xl border border-gray-200 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200"
|
||||||
|
>
|
||||||
|
Назад
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
{[...Array(totalPages)].map((_, index) => {
|
||||||
|
const pageNum = index + 1;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={pageNum}
|
||||||
|
onClick={() => handlePageChange(pageNum)}
|
||||||
|
className={`w-12 h-12 rounded-xl font-semibold transition-all duration-200 ${
|
||||||
|
currentPage === pageNum
|
||||||
|
? 'bg-blue-600 text-white shadow-lg'
|
||||||
|
: 'bg-white text-gray-700 border border-gray-200 hover:bg-gray-50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{pageNum}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => handlePageChange(currentPage + 1)}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
className="px-6 py-3 bg-white text-gray-700 rounded-xl border border-gray-200 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200"
|
||||||
|
>
|
||||||
|
Вперед
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Информация о странице */}
|
||||||
|
<div className="text-center mt-12">
|
||||||
|
<div className="inline-flex items-center px-6 py-3 bg-white rounded-full shadow-lg border border-gray-100">
|
||||||
|
<Eye className="w-5 h-5 text-blue-600 mr-2" />
|
||||||
|
<span className="text-gray-700 font-medium">
|
||||||
|
Страница {currentPage} из {totalPages} • Всего новостей: {totalNews}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<Footer selectedCity={selectedCity} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { NEWS_CATEGORIES } from '@/lib/types';
|
// категории будем подтягивать с API
|
||||||
|
|
||||||
interface NewsDetailPageProps {
|
interface NewsDetailPageProps {
|
||||||
params: Promise<{
|
params: Promise<{
|
||||||
@ -46,7 +46,7 @@ async function getRelatedNews(category: string, currentSlug: string) {
|
|||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
return data.data.news.filter((item: any) => item.slug !== currentSlug);
|
return data.data.news.filter((item: { slug: string }) => item.slug !== currentSlug);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
@ -72,11 +72,18 @@ export default async function NewsDetailPage({ params }: NewsDetailPageProps) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCategoryInfo = (categoryId: string) => {
|
async function getCategoryInfo(categoryId: string) {
|
||||||
return NEWS_CATEGORIES.find(cat => cat.id === categoryId);
|
try {
|
||||||
};
|
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000'}/api/categories`, { cache: 'no-store' });
|
||||||
|
const data = await response.json();
|
||||||
|
if (response.ok && data?.data?.length) {
|
||||||
|
return data.data.find((c: any) => c.slug === categoryId);
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const categoryInfo = getCategoryInfo(news.category);
|
const categoryInfo = await getCategoryInfo(news.category);
|
||||||
|
|
||||||
// Получаем связанные новости (из той же категории, исключая текущую)
|
// Получаем связанные новости (из той же категории, исключая текущую)
|
||||||
const relatedNews = await getRelatedNews(news.category, news.slug);
|
const relatedNews = await getRelatedNews(news.category, news.slug);
|
||||||
@ -218,12 +225,12 @@ export default async function NewsDetailPage({ params }: NewsDetailPageProps) {
|
|||||||
Похожие новости
|
Похожие новости
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-xl text-gray-400">
|
<p className="text-xl text-gray-400">
|
||||||
Другие материалы из категории "{categoryInfo?.name}"
|
Другие материалы из категории "{categoryInfo?.name}"
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||||
{relatedNews.map((relatedNewsItem: any, index: number) => (
|
{relatedNews.map((relatedNewsItem: any) => (
|
||||||
<Link
|
<Link
|
||||||
key={relatedNewsItem.id}
|
key={relatedNewsItem.id}
|
||||||
href={`/news/${relatedNewsItem.slug}`}
|
href={`/news/${relatedNewsItem.slug}`}
|
||||||
|
@ -1,511 +1,21 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useMemo, useEffect } from 'react';
|
import dynamic from 'next/dynamic';
|
||||||
import { useSearchParams, useRouter, usePathname } from 'next/navigation';
|
|
||||||
import Link from 'next/link';
|
|
||||||
import Image from 'next/image';
|
|
||||||
import { NEWS_CATEGORIES } from '@/lib/types';
|
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
|
||||||
import { Search, Filter, Calendar, Eye, ArrowRight, TrendingUp, Star, ArrowLeft } from 'lucide-react';
|
|
||||||
import Header from '@/app/components/Header';
|
|
||||||
import Footer from '@/app/components/Footer';
|
|
||||||
|
|
||||||
const ITEMS_PER_PAGE = 6;
|
const NewsPageDynamic = dynamic(() => import('./NewsPageComponent'), {
|
||||||
|
ssr: false,
|
||||||
type SortOption = 'newest' | 'oldest' | 'alphabetical' | 'featured';
|
loading: () => (
|
||||||
|
<div className="min-h-screen bg-white flex flex-col">
|
||||||
|
<div className="flex-1 flex items-center justify-center pt-20">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="animate-spin rounded-full h-16 w-16 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
||||||
|
<p className="text-gray-600 text-lg">Загрузка новостей...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
export default function NewsPage() {
|
export default function NewsPage() {
|
||||||
// Устанавливаем заголовок страницы
|
return <NewsPageDynamic />;
|
||||||
useEffect(() => {
|
|
||||||
document.title = 'Новости и События - ЦКЭ';
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const [selectedCity, setSelectedCity] = useState<'Москва' | 'Чебоксары'>('Москва');
|
|
||||||
|
|
||||||
// Загружаем город из localStorage
|
|
||||||
useEffect(() => {
|
|
||||||
const savedCity = localStorage.getItem('selectedCity');
|
|
||||||
if (savedCity) {
|
|
||||||
setSelectedCity(savedCity as 'Москва' | 'Чебоксары');
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleCityChange = (city: 'Москва' | 'Чебоксары') => {
|
|
||||||
setSelectedCity(city);
|
|
||||||
localStorage.setItem('selectedCity', city);
|
|
||||||
};
|
|
||||||
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const router = useRouter();
|
|
||||||
const pathname = usePathname();
|
|
||||||
|
|
||||||
const [selectedCategory, setSelectedCategory] = useState<string>(searchParams.get('category') || 'all');
|
|
||||||
const [searchQuery, setSearchQuery] = useState(searchParams.get('search') || '');
|
|
||||||
const [sortBy, setSortBy] = useState<SortOption>((searchParams.get('sort') as SortOption) || 'newest');
|
|
||||||
const [currentPage, setCurrentPage] = useState(parseInt(searchParams.get('page') || '1'));
|
|
||||||
|
|
||||||
// Обновление URL при изменении параметров
|
|
||||||
useEffect(() => {
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
|
|
||||||
if (selectedCategory !== 'all') params.set('category', selectedCategory);
|
|
||||||
if (searchQuery.trim()) params.set('search', searchQuery);
|
|
||||||
if (sortBy !== 'newest') params.set('sort', sortBy);
|
|
||||||
if (currentPage !== 1) params.set('page', currentPage.toString());
|
|
||||||
|
|
||||||
const newUrl = params.toString() ? `${pathname}?${params.toString()}` : pathname;
|
|
||||||
router.replace(newUrl, { scroll: false });
|
|
||||||
}, [selectedCategory, searchQuery, sortBy, currentPage, pathname, router]);
|
|
||||||
|
|
||||||
const [news, setNews] = useState<any[]>([]);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [totalNews, setTotalNews] = useState(0);
|
|
||||||
|
|
||||||
// Загрузка новостей с API
|
|
||||||
useEffect(() => {
|
|
||||||
const loadNews = async () => {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
params.append('page', currentPage.toString());
|
|
||||||
params.append('limit', ITEMS_PER_PAGE.toString());
|
|
||||||
params.append('published', 'true');
|
|
||||||
|
|
||||||
if (selectedCategory !== 'all') {
|
|
||||||
params.append('category', selectedCategory);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchQuery.trim()) {
|
|
||||||
params.append('search', searchQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Преобразуем сортировку в формат API
|
|
||||||
let sortBy_api = 'publishedAt';
|
|
||||||
let sortOrder = 'desc';
|
|
||||||
|
|
||||||
switch (sortBy) {
|
|
||||||
case 'newest':
|
|
||||||
sortBy_api = 'publishedAt';
|
|
||||||
sortOrder = 'desc';
|
|
||||||
break;
|
|
||||||
case 'oldest':
|
|
||||||
sortBy_api = 'publishedAt';
|
|
||||||
sortOrder = 'asc';
|
|
||||||
break;
|
|
||||||
case 'alphabetical':
|
|
||||||
sortBy_api = 'title';
|
|
||||||
sortOrder = 'asc';
|
|
||||||
break;
|
|
||||||
case 'featured':
|
|
||||||
sortBy_api = 'featured';
|
|
||||||
sortOrder = 'desc';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
params.append('sortBy', sortBy_api);
|
|
||||||
params.append('sortOrder', sortOrder);
|
|
||||||
|
|
||||||
const response = await fetch(`/api/news?${params}`);
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.success) {
|
|
||||||
setNews(data.data.news);
|
|
||||||
setTotalNews(data.data.pagination.total);
|
|
||||||
} else {
|
|
||||||
console.error('Error loading news:', data.error);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading news:', error);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loadNews();
|
|
||||||
}, [selectedCategory, searchQuery, sortBy, currentPage]);
|
|
||||||
|
|
||||||
const formatDate = (dateString: string) => {
|
|
||||||
return new Date(dateString).toLocaleDateString('ru-RU', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'long',
|
|
||||||
day: 'numeric'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCategoryInfo = (categoryId: string) => {
|
|
||||||
return NEWS_CATEGORIES.find(cat => cat.id === categoryId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCategoryChange = (category: string) => {
|
|
||||||
setSelectedCategory(category);
|
|
||||||
setCurrentPage(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSearchChange = (query: string) => {
|
|
||||||
setSearchQuery(query);
|
|
||||||
setCurrentPage(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSortChange = (sort: SortOption) => {
|
|
||||||
setSortBy(sort);
|
|
||||||
setCurrentPage(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePageChange = (page: number) => {
|
|
||||||
setCurrentPage(page);
|
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
||||||
};
|
|
||||||
|
|
||||||
// Получаем главную новость (первую в отсортированном списке)
|
|
||||||
const featuredNews = news.find(item => item.featured) || news[0];
|
|
||||||
const otherNews = news.filter(item => item.id !== featuredNews?.id);
|
|
||||||
|
|
||||||
const getSortOptionName = (option: SortOption) => {
|
|
||||||
switch (option) {
|
|
||||||
case 'newest': return 'Сначала новые';
|
|
||||||
case 'oldest': return 'Сначала старые';
|
|
||||||
case 'alphabetical': return 'По алфавиту';
|
|
||||||
case 'featured': return 'Важные первыми';
|
|
||||||
default: return 'Сначала новые';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const totalPages = Math.ceil(totalNews / ITEMS_PER_PAGE);
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-white flex flex-col">
|
|
||||||
<Header selectedCity={selectedCity} onCityChange={handleCityChange} />
|
|
||||||
<main className="flex-1 flex items-center justify-center pt-20">
|
|
||||||
<div className="text-center">
|
|
||||||
<div className="animate-spin rounded-full h-16 w-16 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
|
||||||
<p className="text-gray-600 text-lg">Загрузка новостей...</p>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<Footer selectedCity={selectedCity} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-white flex flex-col">
|
|
||||||
<Header selectedCity={selectedCity} onCityChange={handleCityChange} />
|
|
||||||
|
|
||||||
<main className="flex-1 pt-20">
|
|
||||||
{/* Хлебные крошки */}
|
|
||||||
<div className="bg-gray-50 py-4">
|
|
||||||
<div className="container mx-auto px-4">
|
|
||||||
<div className="flex items-center space-x-2 text-sm text-gray-600">
|
|
||||||
<Link href="/" className="hover:text-blue-600 transition-colors">
|
|
||||||
Главная
|
|
||||||
</Link>
|
|
||||||
<span>/</span>
|
|
||||||
<span className="text-gray-900">Новости</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Заголовок страницы */}
|
|
||||||
<section className="py-16 bg-white">
|
|
||||||
<div className="container mx-auto px-4">
|
|
||||||
<div className="text-center mb-12">
|
|
||||||
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-6">
|
|
||||||
Новости и События
|
|
||||||
</h1>
|
|
||||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
|
||||||
Следите за последними событиями, достижениями и обновлениями нашей компании
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Панель фильтров */}
|
|
||||||
<section className="py-8 bg-gray-50">
|
|
||||||
<div className="container mx-auto px-4">
|
|
||||||
<div className="bg-white rounded-2xl shadow-lg p-6 border border-gray-100">
|
|
||||||
<div className="space-y-6">
|
|
||||||
{/* Поиск */}
|
|
||||||
<div className="relative">
|
|
||||||
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Поиск по заголовку, описанию или содержимому..."
|
|
||||||
value={searchQuery}
|
|
||||||
onChange={(e) => handleSearchChange(e.target.value)}
|
|
||||||
className="w-full pl-12 pr-4 py-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-900 placeholder-gray-500 transition-all duration-200"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Фильтры */}
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
||||||
{/* Категории */}
|
|
||||||
<div>
|
|
||||||
<label className="block text-gray-700 font-semibold mb-3 text-sm">
|
|
||||||
Категория
|
|
||||||
</label>
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
<button
|
|
||||||
onClick={() => handleCategoryChange('all')}
|
|
||||||
className={`px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 ${
|
|
||||||
selectedCategory === 'all'
|
|
||||||
? 'bg-blue-600 text-white shadow-md'
|
|
||||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Все
|
|
||||||
</button>
|
|
||||||
{NEWS_CATEGORIES.map((category) => (
|
|
||||||
<button
|
|
||||||
key={category.id}
|
|
||||||
onClick={() => handleCategoryChange(category.id)}
|
|
||||||
className={`px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 ${
|
|
||||||
selectedCategory === category.id
|
|
||||||
? 'bg-blue-600 text-white shadow-md'
|
|
||||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{category.name}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Сортировка */}
|
|
||||||
<div>
|
|
||||||
<label className="block text-gray-700 font-semibold mb-3 text-sm">
|
|
||||||
Сортировка
|
|
||||||
</label>
|
|
||||||
<select
|
|
||||||
value={sortBy}
|
|
||||||
onChange={(e) => handleSortChange(e.target.value as SortOption)}
|
|
||||||
className="w-full px-4 py-3 border border-gray-200 rounded-xl text-gray-900 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200"
|
|
||||||
>
|
|
||||||
<option value="newest">Сначала новые</option>
|
|
||||||
<option value="oldest">Сначала старые</option>
|
|
||||||
<option value="alphabetical">По алфавиту</option>
|
|
||||||
<option value="featured">Важные первыми</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Статистика */}
|
|
||||||
<div className="flex items-center justify-center lg:justify-end">
|
|
||||||
<div className="bg-gray-100 rounded-xl px-6 py-4 border border-gray-200">
|
|
||||||
<div className="text-center">
|
|
||||||
<div className="text-2xl font-bold text-gray-900">{totalNews}</div>
|
|
||||||
<div className="text-gray-600 text-sm">найдено</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Главная новость */}
|
|
||||||
{currentPage === 1 && featuredNews && (
|
|
||||||
<section className="py-16 bg-white">
|
|
||||||
<div className="container mx-auto px-4">
|
|
||||||
<div className="text-center mb-12">
|
|
||||||
<h2 className="text-3xl font-bold text-gray-900 mb-4">
|
|
||||||
Главная новость
|
|
||||||
</h2>
|
|
||||||
<div className="w-24 h-1 bg-blue-600 mx-auto rounded-full"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<article className="bg-white rounded-3xl shadow-2xl overflow-hidden border border-gray-100 hover:shadow-3xl transition-shadow duration-500">
|
|
||||||
<div className="lg:flex">
|
|
||||||
<div className="lg:w-1/2 relative h-64 lg:h-80">
|
|
||||||
<Image
|
|
||||||
src={featuredNews.imageUrl || '/images/office.jpg'}
|
|
||||||
alt={featuredNews.title}
|
|
||||||
fill
|
|
||||||
className="object-cover"
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent"></div>
|
|
||||||
<div className="absolute top-6 right-6 px-4 py-2 bg-gradient-to-r from-yellow-400 to-orange-500 text-white text-sm font-semibold rounded-full shadow-lg">
|
|
||||||
Важное
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="lg:w-1/2 p-8 lg:p-12">
|
|
||||||
<div className="text-sm text-blue-600 font-semibold mb-4 uppercase tracking-wide">
|
|
||||||
{formatDate(featuredNews.publishedAt)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-6 leading-tight">
|
|
||||||
{featuredNews.title}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p className="text-lg text-gray-600 mb-8 leading-relaxed">
|
|
||||||
{featuredNews.summary}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<Link
|
|
||||||
href={`/news/${featuredNews.slug}`}
|
|
||||||
className="inline-flex items-center px-8 py-4 bg-gradient-to-r from-blue-600 to-indigo-600 text-white font-semibold rounded-xl hover:from-blue-700 hover:to-indigo-700 transition-all duration-300 transform hover:scale-105 shadow-lg hover:shadow-xl"
|
|
||||||
>
|
|
||||||
Читать полностью
|
|
||||||
<ArrowRight className="w-5 h-5 ml-3" />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Сетка новостей */}
|
|
||||||
<section className="py-16 bg-gray-50">
|
|
||||||
<div className="container mx-auto px-4">
|
|
||||||
{news.length > 0 ? (
|
|
||||||
<>
|
|
||||||
<div className="text-center mb-12">
|
|
||||||
<h2 className="text-3xl font-bold text-gray-900 mb-4">
|
|
||||||
{currentPage === 1 && featuredNews ? 'Другие новости' : 'Все новости'}
|
|
||||||
</h2>
|
|
||||||
<div className="w-24 h-1 bg-blue-600 mx-auto rounded-full"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-8 mb-16">
|
|
||||||
{(currentPage === 1 && featuredNews ? otherNews : news).map((newsItem, index) => {
|
|
||||||
const categoryInfo = getCategoryInfo(newsItem.category);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<article
|
|
||||||
key={newsItem.id}
|
|
||||||
className="group bg-white rounded-2xl shadow-lg overflow-hidden hover:shadow-2xl transition-all duration-500 transform hover:-translate-y-2 border border-gray-100"
|
|
||||||
>
|
|
||||||
<div className="relative h-56 overflow-hidden">
|
|
||||||
<Image
|
|
||||||
src={newsItem.imageUrl || '/images/office.jpg'}
|
|
||||||
alt={newsItem.title}
|
|
||||||
fill
|
|
||||||
className="object-cover group-hover:scale-110 transition-transform duration-700"
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-black/30 to-transparent"></div>
|
|
||||||
|
|
||||||
{/* Категория */}
|
|
||||||
{categoryInfo && (
|
|
||||||
<div className="absolute top-4 left-4">
|
|
||||||
<span className={`px-3 py-1 rounded-full text-xs font-semibold text-white ${categoryInfo.color} shadow-lg`}>
|
|
||||||
{categoryInfo.name}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Дата */}
|
|
||||||
<div className="absolute bottom-4 right-4">
|
|
||||||
<span className="px-3 py-1 bg-black/50 text-white text-xs rounded-full">
|
|
||||||
{formatDate(newsItem.publishedAt)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-6">
|
|
||||||
<h3 className="text-xl font-bold text-gray-900 mb-3 line-clamp-2 group-hover:text-blue-600 transition-colors duration-300">
|
|
||||||
{newsItem.title}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p className="text-gray-600 mb-6 line-clamp-3 leading-relaxed">
|
|
||||||
{newsItem.summary}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<Link
|
|
||||||
href={`/news/${newsItem.slug}`}
|
|
||||||
className="inline-flex items-center text-blue-600 hover:text-blue-800 font-semibold transition-colors duration-300"
|
|
||||||
>
|
|
||||||
Читать далее
|
|
||||||
<ArrowRight className="w-4 h-4 ml-2" />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div className="text-center py-20">
|
|
||||||
<div className="bg-white rounded-3xl shadow-xl p-12 max-w-md mx-auto border border-gray-100">
|
|
||||||
<div className="w-20 h-20 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
|
||||||
<Search className="w-10 h-10 text-gray-400" />
|
|
||||||
</div>
|
|
||||||
<h3 className="text-2xl font-bold text-gray-900 mb-4">
|
|
||||||
Новостей не найдено
|
|
||||||
</h3>
|
|
||||||
<p className="text-gray-600 text-lg mb-8">
|
|
||||||
Попробуйте изменить фильтры или поисковый запрос
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedCategory('all');
|
|
||||||
setSearchQuery('');
|
|
||||||
setSortBy('newest');
|
|
||||||
setCurrentPage(1);
|
|
||||||
}}
|
|
||||||
className="px-8 py-4 bg-blue-600 text-white font-semibold rounded-xl hover:bg-blue-700 transition-colors duration-300"
|
|
||||||
>
|
|
||||||
Сбросить фильтры
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Пагинация */}
|
|
||||||
{totalPages > 1 && (
|
|
||||||
<div className="flex justify-center items-center space-x-2 mt-16">
|
|
||||||
<button
|
|
||||||
onClick={() => handlePageChange(currentPage - 1)}
|
|
||||||
disabled={currentPage === 1}
|
|
||||||
className="px-6 py-3 bg-white text-gray-700 rounded-xl border border-gray-200 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200"
|
|
||||||
>
|
|
||||||
Назад
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div className="flex space-x-2">
|
|
||||||
{[...Array(totalPages)].map((_, index) => {
|
|
||||||
const pageNum = index + 1;
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={pageNum}
|
|
||||||
onClick={() => handlePageChange(pageNum)}
|
|
||||||
className={`w-12 h-12 rounded-xl font-semibold transition-all duration-200 ${
|
|
||||||
currentPage === pageNum
|
|
||||||
? 'bg-blue-600 text-white shadow-lg'
|
|
||||||
: 'bg-white text-gray-700 border border-gray-200 hover:bg-gray-50'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{pageNum}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => handlePageChange(currentPage + 1)}
|
|
||||||
disabled={currentPage === totalPages}
|
|
||||||
className="px-6 py-3 bg-white text-gray-700 rounded-xl border border-gray-200 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200"
|
|
||||||
>
|
|
||||||
Вперед
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Информация о странице */}
|
|
||||||
<div className="text-center mt-12">
|
|
||||||
<div className="inline-flex items-center px-6 py-3 bg-white rounded-full shadow-lg border border-gray-100">
|
|
||||||
<Eye className="w-5 h-5 text-blue-600 mr-2" />
|
|
||||||
<span className="text-gray-700 font-medium">
|
|
||||||
Страница {currentPage} из {totalPages} • Всего новостей: {totalNews}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<Footer selectedCity={selectedCity} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
@ -1,12 +1,32 @@
|
|||||||
version: '3.8'
|
version: "3.8"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
|
env_file:
|
||||||
|
- stack.env
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- "3006:3000"
|
- "3006:3000"
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=${NODE_ENV:-production}
|
||||||
|
- DATABASE_URL=${DATABASE_URL}
|
||||||
|
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
|
||||||
|
- S3_ENDPOINT=${S3_ENDPOINT}
|
||||||
|
- S3_REGION=${S3_REGION}
|
||||||
|
- S3_ACCESS_KEY_ID=${S3_ACCESS_KEY_ID}
|
||||||
|
- S3_SECRET_ACCESS_KEY=${S3_SECRET_ACCESS_KEY}
|
||||||
|
- S3_BUCKET_NAME=${S3_BUCKET_NAME}
|
||||||
|
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
|
||||||
|
- TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID}
|
||||||
|
- NEXTAUTH_URL=${NEXTAUTH_URL}
|
||||||
|
- PORT=3000
|
||||||
|
- HOSTNAME=0.0.0.0
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -q --tries=1 --spider http://localhost:3000/api/health || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export const TELEGRAM_CONFIG = {
|
export const TELEGRAM_CONFIG = {
|
||||||
BOT_TOKEN: '7802786776:AAGQGYf2BxuBFmZOhRcLTX52KnUEYifTKZY',
|
BOT_TOKEN: process.env.TELEGRAM_BOT_TOKEN || '',
|
||||||
CHAT_ID: '-1002321880357',
|
CHAT_ID: process.env.TELEGRAM_CHAT_ID || '',
|
||||||
};
|
};
|
||||||
|
114
lib/hooks/useFileUpload.ts
Normal file
114
lib/hooks/useFileUpload.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import { useState, useCallback } from 'react';
|
||||||
|
|
||||||
|
export interface UploadOptions {
|
||||||
|
folder?: string;
|
||||||
|
maxSize?: number; // в MB
|
||||||
|
allowedTypes?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UploadResult {
|
||||||
|
key: string;
|
||||||
|
url: string;
|
||||||
|
publicUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseFileUploadReturn {
|
||||||
|
uploadFile: (file: File, options?: UploadOptions) => Promise<UploadResult>;
|
||||||
|
deleteFile: (url: string) => Promise<void>;
|
||||||
|
isUploading: boolean;
|
||||||
|
error: string | null;
|
||||||
|
clearError: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFileUpload(): UseFileUploadReturn {
|
||||||
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const clearError = useCallback(() => {
|
||||||
|
setError(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const uploadFile = useCallback(async (
|
||||||
|
file: File,
|
||||||
|
options: UploadOptions = {}
|
||||||
|
): Promise<UploadResult> => {
|
||||||
|
const {
|
||||||
|
folder = 'uploads',
|
||||||
|
maxSize = 10,
|
||||||
|
allowedTypes = [
|
||||||
|
'image/jpeg',
|
||||||
|
'image/png',
|
||||||
|
'image/gif',
|
||||||
|
'image/webp',
|
||||||
|
'image/svg+xml',
|
||||||
|
'application/pdf',
|
||||||
|
'application/msword',
|
||||||
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||||
|
]
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
setIsUploading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Валидация размера файла
|
||||||
|
if (file.size > maxSize * 1024 * 1024) {
|
||||||
|
throw new Error(`Файл слишком большой. Максимальный размер: ${maxSize} MB`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Валидация типа файла
|
||||||
|
if (!allowedTypes.includes(file.type)) {
|
||||||
|
throw new Error(`Неподдерживаемый тип файла. Разрешены: ${allowedTypes.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
formData.append('folder', folder);
|
||||||
|
|
||||||
|
const response = await fetch('/api/upload', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(result.error || 'Ошибка при загрузке файла');
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.data;
|
||||||
|
} catch (err) {
|
||||||
|
const errorMessage = err instanceof Error ? err.message : 'Ошибка при загрузке файла';
|
||||||
|
setError(errorMessage);
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
} finally {
|
||||||
|
setIsUploading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const deleteFile = useCallback(async (url: string): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/upload?url=${encodeURIComponent(url)}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(result.error || 'Ошибка при удалении файла');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const errorMessage = err instanceof Error ? err.message : 'Ошибка при удалении файла';
|
||||||
|
setError(errorMessage);
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
uploadFile,
|
||||||
|
deleteFile,
|
||||||
|
isUploading,
|
||||||
|
error,
|
||||||
|
clearError,
|
||||||
|
};
|
||||||
|
}
|
149
lib/s3.ts
Normal file
149
lib/s3.ts
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import { S3Client, PutObjectCommand, DeleteObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
|
||||||
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||||
|
|
||||||
|
// Инициализация S3 клиента
|
||||||
|
const s3Client = new S3Client({
|
||||||
|
endpoint: process.env.S3_ENDPOINT,
|
||||||
|
region: process.env.S3_REGION || 'ru-1',
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: process.env.S3_ACCESS_KEY_ID!,
|
||||||
|
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
|
||||||
|
},
|
||||||
|
forcePathStyle: true, // Важно для совместимости с некоторыми S3-совместимыми сервисами
|
||||||
|
});
|
||||||
|
|
||||||
|
const BUCKET_NAME = process.env.S3_BUCKET_NAME!;
|
||||||
|
|
||||||
|
export interface UploadResult {
|
||||||
|
key: string;
|
||||||
|
url: string;
|
||||||
|
publicUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Загружает файл в S3 хранилище
|
||||||
|
* @param file - Файл для загрузки
|
||||||
|
* @param folder - Папка в S3 (например, 'images', 'documents')
|
||||||
|
* @param fileName - Имя файла (опционально, если не указано - генерируется автоматически)
|
||||||
|
* @returns Promise<UploadResult>
|
||||||
|
*/
|
||||||
|
export async function uploadFileToS3(
|
||||||
|
file: Buffer | Uint8Array,
|
||||||
|
contentType: string,
|
||||||
|
folder: string = 'uploads',
|
||||||
|
fileName?: string
|
||||||
|
): Promise<UploadResult> {
|
||||||
|
try {
|
||||||
|
// Генерируем уникальное имя файла, если не указано
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const randomString = Math.random().toString(36).substring(2, 15);
|
||||||
|
const extension = contentType.split('/')[1];
|
||||||
|
const finalFileName = fileName || `${timestamp}_${randomString}.${extension}`;
|
||||||
|
|
||||||
|
// Формируем ключ для S3
|
||||||
|
const key = `${folder}/${finalFileName}`;
|
||||||
|
|
||||||
|
// Команда для загрузки файла
|
||||||
|
const command = new PutObjectCommand({
|
||||||
|
Bucket: BUCKET_NAME,
|
||||||
|
Key: key,
|
||||||
|
Body: file,
|
||||||
|
ContentType: contentType,
|
||||||
|
// Делаем файл публично доступным
|
||||||
|
ACL: 'public-read',
|
||||||
|
});
|
||||||
|
|
||||||
|
await s3Client.send(command);
|
||||||
|
|
||||||
|
// Формируем URL для доступа к файлу
|
||||||
|
const publicUrl = `${process.env.S3_ENDPOINT}/${BUCKET_NAME}/${key}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
url: publicUrl,
|
||||||
|
publicUrl,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при загрузке файла в S3:', error);
|
||||||
|
throw new Error('Не удалось загрузить файл');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Удаляет файл из S3 хранилища
|
||||||
|
* @param key - Ключ файла в S3
|
||||||
|
* @returns Promise<void>
|
||||||
|
*/
|
||||||
|
export async function deleteFileFromS3(key: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const command = new DeleteObjectCommand({
|
||||||
|
Bucket: BUCKET_NAME,
|
||||||
|
Key: key,
|
||||||
|
});
|
||||||
|
|
||||||
|
await s3Client.send(command);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при удалении файла из S3:', error);
|
||||||
|
throw new Error('Не удалось удалить файл');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает подписанный URL для временного доступа к файлу
|
||||||
|
* @param key - Ключ файла в S3
|
||||||
|
* @param expiresIn - Время жизни ссылки в секундах (по умолчанию 1 час)
|
||||||
|
* @returns Promise<string>
|
||||||
|
*/
|
||||||
|
export async function getSignedUrlFromS3(key: string, expiresIn: number = 3600): Promise<string> {
|
||||||
|
try {
|
||||||
|
const command = new GetObjectCommand({
|
||||||
|
Bucket: BUCKET_NAME,
|
||||||
|
Key: key,
|
||||||
|
});
|
||||||
|
|
||||||
|
const signedUrl = await getSignedUrl(s3Client, command, { expiresIn });
|
||||||
|
return signedUrl;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при получении подписанного URL:', error);
|
||||||
|
throw new Error('Не удалось получить ссылку на файл');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает публичный URL файла
|
||||||
|
* @param key - Ключ файла в S3
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
export function getPublicUrlFromS3(key: string): string {
|
||||||
|
return `${process.env.S3_ENDPOINT}/${BUCKET_NAME}/${key}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Извлекает ключ файла из публичного URL
|
||||||
|
* @param url - Публичный URL файла
|
||||||
|
* @returns string | null
|
||||||
|
*/
|
||||||
|
export function extractKeyFromUrl(url: string): string | null {
|
||||||
|
try {
|
||||||
|
const baseUrl = `${process.env.S3_ENDPOINT}/${BUCKET_NAME}/`;
|
||||||
|
if (url.startsWith(baseUrl)) {
|
||||||
|
return url.replace(baseUrl, '');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при извлечении ключа из URL:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, является ли URL ссылкой на файл в S3
|
||||||
|
* @param url - URL для проверки
|
||||||
|
* @returns boolean
|
||||||
|
*/
|
||||||
|
export function isS3Url(url: string): boolean {
|
||||||
|
const baseUrl = `${process.env.S3_ENDPOINT}/${BUCKET_NAME}/`;
|
||||||
|
return url.startsWith(baseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default s3Client;
|
@ -1,9 +1,29 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
output: 'standalone',
|
eslint: {
|
||||||
images: {
|
// Отключаем ESLint при сборке для продакшена
|
||||||
unoptimized: true,
|
ignoreDuringBuilds: true,
|
||||||
},
|
},
|
||||||
|
typescript: {
|
||||||
|
// Игнорируем ошибки TypeScript при сборке для продакшена
|
||||||
|
ignoreBuildErrors: true,
|
||||||
|
},
|
||||||
|
images: {
|
||||||
|
domains: ['localhost'],
|
||||||
|
remotePatterns: [
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: '**',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: 's3.twcstorage.ru',
|
||||||
|
port: '',
|
||||||
|
pathname: '/**',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
output: 'standalone',
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = nextConfig;
|
module.exports = nextConfig;
|
||||||
|
2073
package-lock.json
generated
2073
package-lock.json
generated
@ -8,16 +8,22 @@
|
|||||||
"name": "cke",
|
"name": "cke",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-s3": "^3.844.0",
|
||||||
|
"@aws-sdk/s3-request-presigner": "^3.844.0",
|
||||||
"@prisma/client": "^6.11.1",
|
"@prisma/client": "^6.11.1",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
||||||
"@radix-ui/react-slot": "^1.1.2",
|
"@radix-ui/react-slot": "^1.1.2",
|
||||||
"@shadcn/ui": "^0.0.4",
|
"@shadcn/ui": "^0.0.4",
|
||||||
|
"@types/multer": "^2.0.0",
|
||||||
|
"aws-sdk": "^2.1692.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"dotenv": "^17.2.0",
|
||||||
"framer-motion": "^12.3.1",
|
"framer-motion": "^12.3.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"lucide-react": "^0.474.0",
|
"lucide-react": "^0.474.0",
|
||||||
|
"multer": "^2.0.1",
|
||||||
"next": "^15.3.1",
|
"next": "^15.3.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
@ -55,6 +61,898 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@aws-crypto/crc32": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-crypto/util": "^5.2.0",
|
||||||
|
"@aws-sdk/types": "^3.222.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-crypto/crc32c": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-crypto/util": "^5.2.0",
|
||||||
|
"@aws-sdk/types": "^3.222.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-crypto/sha1-browser": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-crypto/supports-web-crypto": "^5.2.0",
|
||||||
|
"@aws-crypto/util": "^5.2.0",
|
||||||
|
"@aws-sdk/types": "^3.222.0",
|
||||||
|
"@aws-sdk/util-locate-window": "^3.0.0",
|
||||||
|
"@smithy/util-utf8": "^2.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/is-array-buffer": "^2.2.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/util-buffer-from": "^2.2.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-crypto/sha256-browser": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-crypto/sha256-js": "^5.2.0",
|
||||||
|
"@aws-crypto/supports-web-crypto": "^5.2.0",
|
||||||
|
"@aws-crypto/util": "^5.2.0",
|
||||||
|
"@aws-sdk/types": "^3.222.0",
|
||||||
|
"@aws-sdk/util-locate-window": "^3.0.0",
|
||||||
|
"@smithy/util-utf8": "^2.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/is-array-buffer": "^2.2.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/util-buffer-from": "^2.2.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-crypto/sha256-js": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-crypto/util": "^5.2.0",
|
||||||
|
"@aws-sdk/types": "^3.222.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-crypto/supports-web-crypto": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-crypto/util": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/types": "^3.222.0",
|
||||||
|
"@smithy/util-utf8": "^2.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/is-array-buffer": "^2.2.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/util-buffer-from": "^2.2.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/client-s3": {
|
||||||
|
"version": "3.844.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.844.0.tgz",
|
||||||
|
"integrity": "sha512-Yhp8+U4KFVQqL6phZ5yrHF5PdCvKWbYtLSS+egAfAW+N5w78amhbZcctervj59uqOZHMGDWXuDBklN+7eVfasg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-crypto/sha1-browser": "5.2.0",
|
||||||
|
"@aws-crypto/sha256-browser": "5.2.0",
|
||||||
|
"@aws-crypto/sha256-js": "5.2.0",
|
||||||
|
"@aws-sdk/core": "3.844.0",
|
||||||
|
"@aws-sdk/credential-provider-node": "3.844.0",
|
||||||
|
"@aws-sdk/middleware-bucket-endpoint": "3.840.0",
|
||||||
|
"@aws-sdk/middleware-expect-continue": "3.840.0",
|
||||||
|
"@aws-sdk/middleware-flexible-checksums": "3.844.0",
|
||||||
|
"@aws-sdk/middleware-host-header": "3.840.0",
|
||||||
|
"@aws-sdk/middleware-location-constraint": "3.840.0",
|
||||||
|
"@aws-sdk/middleware-logger": "3.840.0",
|
||||||
|
"@aws-sdk/middleware-recursion-detection": "3.840.0",
|
||||||
|
"@aws-sdk/middleware-sdk-s3": "3.844.0",
|
||||||
|
"@aws-sdk/middleware-ssec": "3.840.0",
|
||||||
|
"@aws-sdk/middleware-user-agent": "3.844.0",
|
||||||
|
"@aws-sdk/region-config-resolver": "3.840.0",
|
||||||
|
"@aws-sdk/signature-v4-multi-region": "3.844.0",
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@aws-sdk/util-endpoints": "3.844.0",
|
||||||
|
"@aws-sdk/util-user-agent-browser": "3.840.0",
|
||||||
|
"@aws-sdk/util-user-agent-node": "3.844.0",
|
||||||
|
"@aws-sdk/xml-builder": "3.821.0",
|
||||||
|
"@smithy/config-resolver": "^4.1.4",
|
||||||
|
"@smithy/core": "^3.7.0",
|
||||||
|
"@smithy/eventstream-serde-browser": "^4.0.4",
|
||||||
|
"@smithy/eventstream-serde-config-resolver": "^4.1.2",
|
||||||
|
"@smithy/eventstream-serde-node": "^4.0.4",
|
||||||
|
"@smithy/fetch-http-handler": "^5.1.0",
|
||||||
|
"@smithy/hash-blob-browser": "^4.0.4",
|
||||||
|
"@smithy/hash-node": "^4.0.4",
|
||||||
|
"@smithy/hash-stream-node": "^4.0.4",
|
||||||
|
"@smithy/invalid-dependency": "^4.0.4",
|
||||||
|
"@smithy/md5-js": "^4.0.4",
|
||||||
|
"@smithy/middleware-content-length": "^4.0.4",
|
||||||
|
"@smithy/middleware-endpoint": "^4.1.14",
|
||||||
|
"@smithy/middleware-retry": "^4.1.15",
|
||||||
|
"@smithy/middleware-serde": "^4.0.8",
|
||||||
|
"@smithy/middleware-stack": "^4.0.4",
|
||||||
|
"@smithy/node-config-provider": "^4.1.3",
|
||||||
|
"@smithy/node-http-handler": "^4.1.0",
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/smithy-client": "^4.4.6",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/url-parser": "^4.0.4",
|
||||||
|
"@smithy/util-base64": "^4.0.0",
|
||||||
|
"@smithy/util-body-length-browser": "^4.0.0",
|
||||||
|
"@smithy/util-body-length-node": "^4.0.0",
|
||||||
|
"@smithy/util-defaults-mode-browser": "^4.0.22",
|
||||||
|
"@smithy/util-defaults-mode-node": "^4.0.22",
|
||||||
|
"@smithy/util-endpoints": "^3.0.6",
|
||||||
|
"@smithy/util-middleware": "^4.0.4",
|
||||||
|
"@smithy/util-retry": "^4.0.6",
|
||||||
|
"@smithy/util-stream": "^4.2.3",
|
||||||
|
"@smithy/util-utf8": "^4.0.0",
|
||||||
|
"@smithy/util-waiter": "^4.0.6",
|
||||||
|
"@types/uuid": "^9.0.1",
|
||||||
|
"tslib": "^2.6.2",
|
||||||
|
"uuid": "^9.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/client-sso": {
|
||||||
|
"version": "3.844.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.844.0.tgz",
|
||||||
|
"integrity": "sha512-FktodSx+pfUfIqMjoNwZ6t1xqq/G3cfT7I4JJ0HKHoIIZdoCHQB52x0OzKDtHDJAnEQPInasdPS8PorZBZtHmg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-crypto/sha256-browser": "5.2.0",
|
||||||
|
"@aws-crypto/sha256-js": "5.2.0",
|
||||||
|
"@aws-sdk/core": "3.844.0",
|
||||||
|
"@aws-sdk/middleware-host-header": "3.840.0",
|
||||||
|
"@aws-sdk/middleware-logger": "3.840.0",
|
||||||
|
"@aws-sdk/middleware-recursion-detection": "3.840.0",
|
||||||
|
"@aws-sdk/middleware-user-agent": "3.844.0",
|
||||||
|
"@aws-sdk/region-config-resolver": "3.840.0",
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@aws-sdk/util-endpoints": "3.844.0",
|
||||||
|
"@aws-sdk/util-user-agent-browser": "3.840.0",
|
||||||
|
"@aws-sdk/util-user-agent-node": "3.844.0",
|
||||||
|
"@smithy/config-resolver": "^4.1.4",
|
||||||
|
"@smithy/core": "^3.7.0",
|
||||||
|
"@smithy/fetch-http-handler": "^5.1.0",
|
||||||
|
"@smithy/hash-node": "^4.0.4",
|
||||||
|
"@smithy/invalid-dependency": "^4.0.4",
|
||||||
|
"@smithy/middleware-content-length": "^4.0.4",
|
||||||
|
"@smithy/middleware-endpoint": "^4.1.14",
|
||||||
|
"@smithy/middleware-retry": "^4.1.15",
|
||||||
|
"@smithy/middleware-serde": "^4.0.8",
|
||||||
|
"@smithy/middleware-stack": "^4.0.4",
|
||||||
|
"@smithy/node-config-provider": "^4.1.3",
|
||||||
|
"@smithy/node-http-handler": "^4.1.0",
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/smithy-client": "^4.4.6",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/url-parser": "^4.0.4",
|
||||||
|
"@smithy/util-base64": "^4.0.0",
|
||||||
|
"@smithy/util-body-length-browser": "^4.0.0",
|
||||||
|
"@smithy/util-body-length-node": "^4.0.0",
|
||||||
|
"@smithy/util-defaults-mode-browser": "^4.0.22",
|
||||||
|
"@smithy/util-defaults-mode-node": "^4.0.22",
|
||||||
|
"@smithy/util-endpoints": "^3.0.6",
|
||||||
|
"@smithy/util-middleware": "^4.0.4",
|
||||||
|
"@smithy/util-retry": "^4.0.6",
|
||||||
|
"@smithy/util-utf8": "^4.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/core": {
|
||||||
|
"version": "3.844.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.844.0.tgz",
|
||||||
|
"integrity": "sha512-pfpI54bG5Xf2NkqrDBC2REStXlDXNCw/whORhkEs+Tp5exU872D5QKguzjPA6hH+8Pvbq1qgt5zXMbduISTHJw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@aws-sdk/xml-builder": "3.821.0",
|
||||||
|
"@smithy/core": "^3.7.0",
|
||||||
|
"@smithy/node-config-provider": "^4.1.3",
|
||||||
|
"@smithy/property-provider": "^4.0.4",
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/signature-v4": "^5.1.2",
|
||||||
|
"@smithy/smithy-client": "^4.4.6",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/util-base64": "^4.0.0",
|
||||||
|
"@smithy/util-body-length-browser": "^4.0.0",
|
||||||
|
"@smithy/util-middleware": "^4.0.4",
|
||||||
|
"@smithy/util-utf8": "^4.0.0",
|
||||||
|
"fast-xml-parser": "5.2.5",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/credential-provider-env": {
|
||||||
|
"version": "3.844.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.844.0.tgz",
|
||||||
|
"integrity": "sha512-WB94Ox86MqcZ4CnRjKgopzaSuZH4hMP0GqdOxG4s1it1lRWOIPOHOC1dPiM0Zbj1uqITIhbXUQVXyP/uaJeNkw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/core": "3.844.0",
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/property-provider": "^4.0.4",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/credential-provider-http": {
|
||||||
|
"version": "3.844.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.844.0.tgz",
|
||||||
|
"integrity": "sha512-e+efVqfkhpM8zxYeiLNgTUlX+tmtXzVm3bw1A02U9Z9cWBHyQNb8pi90M7QniLoqRURY1B0C2JqkOE61gd4KNg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/core": "3.844.0",
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/fetch-http-handler": "^5.1.0",
|
||||||
|
"@smithy/node-http-handler": "^4.1.0",
|
||||||
|
"@smithy/property-provider": "^4.0.4",
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/smithy-client": "^4.4.6",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/util-stream": "^4.2.3",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/credential-provider-ini": {
|
||||||
|
"version": "3.844.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.844.0.tgz",
|
||||||
|
"integrity": "sha512-jc5ArGz2HfAx5QPXD+Ep36+QWyCKzl2TG6Vtl87/vljfLhVD0gEHv8fRsqWEp3Rc6hVfKnCjLW5ayR2HYcow9w==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/core": "3.844.0",
|
||||||
|
"@aws-sdk/credential-provider-env": "3.844.0",
|
||||||
|
"@aws-sdk/credential-provider-http": "3.844.0",
|
||||||
|
"@aws-sdk/credential-provider-process": "3.844.0",
|
||||||
|
"@aws-sdk/credential-provider-sso": "3.844.0",
|
||||||
|
"@aws-sdk/credential-provider-web-identity": "3.844.0",
|
||||||
|
"@aws-sdk/nested-clients": "3.844.0",
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/credential-provider-imds": "^4.0.6",
|
||||||
|
"@smithy/property-provider": "^4.0.4",
|
||||||
|
"@smithy/shared-ini-file-loader": "^4.0.4",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/credential-provider-node": {
|
||||||
|
"version": "3.844.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.844.0.tgz",
|
||||||
|
"integrity": "sha512-pUqB0StTNyW0R03XjTA3wrQZcie/7FJKSXlYHue921ZXuhLOZpzyDkLNfdRsZTcEoYYWVPSmyS+Eu/g5yVsBNA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/credential-provider-env": "3.844.0",
|
||||||
|
"@aws-sdk/credential-provider-http": "3.844.0",
|
||||||
|
"@aws-sdk/credential-provider-ini": "3.844.0",
|
||||||
|
"@aws-sdk/credential-provider-process": "3.844.0",
|
||||||
|
"@aws-sdk/credential-provider-sso": "3.844.0",
|
||||||
|
"@aws-sdk/credential-provider-web-identity": "3.844.0",
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/credential-provider-imds": "^4.0.6",
|
||||||
|
"@smithy/property-provider": "^4.0.4",
|
||||||
|
"@smithy/shared-ini-file-loader": "^4.0.4",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/credential-provider-process": {
|
||||||
|
"version": "3.844.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.844.0.tgz",
|
||||||
|
"integrity": "sha512-VCI8XvIDt2WBfk5Gi/wXKPcWTS3OkAbovB66oKcNQalllH8ESDg4SfLNhchdnN8A5sDGj6tIBJ19nk+dQ6GaqQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/core": "3.844.0",
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/property-provider": "^4.0.4",
|
||||||
|
"@smithy/shared-ini-file-loader": "^4.0.4",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/credential-provider-sso": {
|
||||||
|
"version": "3.844.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.844.0.tgz",
|
||||||
|
"integrity": "sha512-UNp/uWufGlb5nWa4dpc6uQnDOB/9ysJJFG95ACowNVL9XWfi1LJO7teKrqNkVhq0CzSJS1tCt3FvX4UfM+aN1g==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-sso": "3.844.0",
|
||||||
|
"@aws-sdk/core": "3.844.0",
|
||||||
|
"@aws-sdk/token-providers": "3.844.0",
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/property-provider": "^4.0.4",
|
||||||
|
"@smithy/shared-ini-file-loader": "^4.0.4",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/credential-provider-web-identity": {
|
||||||
|
"version": "3.844.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.844.0.tgz",
|
||||||
|
"integrity": "sha512-iDmX4pPmatjttIScdspZRagaFnCjpHZIEEwTyKdXxUaU0iAOSXF8ecrCEvutETvImPOC86xdrq+MPacJOnMzUA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/core": "3.844.0",
|
||||||
|
"@aws-sdk/nested-clients": "3.844.0",
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/property-provider": "^4.0.4",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/middleware-bucket-endpoint": {
|
||||||
|
"version": "3.840.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.840.0.tgz",
|
||||||
|
"integrity": "sha512-+gkQNtPwcSMmlwBHFd4saVVS11In6ID1HczNzpM3MXKXRBfSlbZJbCt6wN//AZ8HMklZEik4tcEOG0qa9UY8SQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@aws-sdk/util-arn-parser": "3.804.0",
|
||||||
|
"@smithy/node-config-provider": "^4.1.3",
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/util-config-provider": "^4.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/middleware-expect-continue": {
|
||||||
|
"version": "3.840.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.840.0.tgz",
|
||||||
|
"integrity": "sha512-iJg2r6FKsKKvdiU4oCOuCf7Ro/YE0Q2BT/QyEZN3/Rt8Nr4SAZiQOlcBXOCpGvuIKOEAhvDOUnW3aDHL01PdVw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/middleware-flexible-checksums": {
|
||||||
|
"version": "3.844.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.844.0.tgz",
|
||||||
|
"integrity": "sha512-LCImZd1hpM0cegfdpgZyK6x4on4Ky+c9XCFURfE4wil1J9HXf6OP4KsfHQwt1yIkMEbFqvd/ab2I5fmp7S7aFA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-crypto/crc32": "5.2.0",
|
||||||
|
"@aws-crypto/crc32c": "5.2.0",
|
||||||
|
"@aws-crypto/util": "5.2.0",
|
||||||
|
"@aws-sdk/core": "3.844.0",
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/is-array-buffer": "^4.0.0",
|
||||||
|
"@smithy/node-config-provider": "^4.1.3",
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/util-middleware": "^4.0.4",
|
||||||
|
"@smithy/util-stream": "^4.2.3",
|
||||||
|
"@smithy/util-utf8": "^4.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/middleware-host-header": {
|
||||||
|
"version": "3.840.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.840.0.tgz",
|
||||||
|
"integrity": "sha512-ub+hXJAbAje94+Ya6c6eL7sYujoE8D4Bumu1NUI8TXjUhVVn0HzVWQjpRLshdLsUp1AW7XyeJaxyajRaJQ8+Xg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/middleware-location-constraint": {
|
||||||
|
"version": "3.840.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.840.0.tgz",
|
||||||
|
"integrity": "sha512-KVLD0u0YMF3aQkVF8bdyHAGWSUY6N1Du89htTLgqCcIhSxxAJ9qifrosVZ9jkAzqRW99hcufyt2LylcVU2yoKQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/middleware-logger": {
|
||||||
|
"version": "3.840.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.840.0.tgz",
|
||||||
|
"integrity": "sha512-lSV8FvjpdllpGaRspywss4CtXV8M7NNNH+2/j86vMH+YCOZ6fu2T/TyFd/tHwZ92vDfHctWkRbQxg0bagqwovA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/middleware-recursion-detection": {
|
||||||
|
"version": "3.840.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.840.0.tgz",
|
||||||
|
"integrity": "sha512-Gu7lGDyfddyhIkj1Z1JtrY5NHb5+x/CRiB87GjaSrKxkDaydtX2CU977JIABtt69l9wLbcGDIQ+W0uJ5xPof7g==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/middleware-sdk-s3": {
|
||||||
|
"version": "3.844.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.844.0.tgz",
|
||||||
|
"integrity": "sha512-vOD5reqZszXBWMbZFN3EUar203o2i8gcoTdrymY4GMsAPDsh0k8yd3VJRNPuxT/017tP6G+rQepOGzna4umung==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/core": "3.844.0",
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@aws-sdk/util-arn-parser": "3.804.0",
|
||||||
|
"@smithy/core": "^3.7.0",
|
||||||
|
"@smithy/node-config-provider": "^4.1.3",
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/signature-v4": "^5.1.2",
|
||||||
|
"@smithy/smithy-client": "^4.4.6",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/util-config-provider": "^4.0.0",
|
||||||
|
"@smithy/util-middleware": "^4.0.4",
|
||||||
|
"@smithy/util-stream": "^4.2.3",
|
||||||
|
"@smithy/util-utf8": "^4.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/middleware-ssec": {
|
||||||
|
"version": "3.840.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.840.0.tgz",
|
||||||
|
"integrity": "sha512-CBZP9t1QbjDFGOrtnUEHL1oAvmnCUUm7p0aPNbIdSzNtH42TNKjPRN3TuEIJDGjkrqpL3MXyDSmNayDcw/XW7Q==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/middleware-user-agent": {
|
||||||
|
"version": "3.844.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.844.0.tgz",
|
||||||
|
"integrity": "sha512-SIbDNUL6ZYXPj5Tk0qEz05sW9kNS1Gl3/wNWEmH+AuUACipkyIeKKWzD6z5433MllETh73vtka/JQF3g7AuZww==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/core": "3.844.0",
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@aws-sdk/util-endpoints": "3.844.0",
|
||||||
|
"@smithy/core": "^3.7.0",
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/nested-clients": {
|
||||||
|
"version": "3.844.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.844.0.tgz",
|
||||||
|
"integrity": "sha512-p2XILWc7AcevUSpBg2VtQrk79eWQC4q2JsCSY7HxKpFLZB4mMOfmiTyYkR1gEA6AttK/wpCOtfz+hi1/+z2V1A==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-crypto/sha256-browser": "5.2.0",
|
||||||
|
"@aws-crypto/sha256-js": "5.2.0",
|
||||||
|
"@aws-sdk/core": "3.844.0",
|
||||||
|
"@aws-sdk/middleware-host-header": "3.840.0",
|
||||||
|
"@aws-sdk/middleware-logger": "3.840.0",
|
||||||
|
"@aws-sdk/middleware-recursion-detection": "3.840.0",
|
||||||
|
"@aws-sdk/middleware-user-agent": "3.844.0",
|
||||||
|
"@aws-sdk/region-config-resolver": "3.840.0",
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@aws-sdk/util-endpoints": "3.844.0",
|
||||||
|
"@aws-sdk/util-user-agent-browser": "3.840.0",
|
||||||
|
"@aws-sdk/util-user-agent-node": "3.844.0",
|
||||||
|
"@smithy/config-resolver": "^4.1.4",
|
||||||
|
"@smithy/core": "^3.7.0",
|
||||||
|
"@smithy/fetch-http-handler": "^5.1.0",
|
||||||
|
"@smithy/hash-node": "^4.0.4",
|
||||||
|
"@smithy/invalid-dependency": "^4.0.4",
|
||||||
|
"@smithy/middleware-content-length": "^4.0.4",
|
||||||
|
"@smithy/middleware-endpoint": "^4.1.14",
|
||||||
|
"@smithy/middleware-retry": "^4.1.15",
|
||||||
|
"@smithy/middleware-serde": "^4.0.8",
|
||||||
|
"@smithy/middleware-stack": "^4.0.4",
|
||||||
|
"@smithy/node-config-provider": "^4.1.3",
|
||||||
|
"@smithy/node-http-handler": "^4.1.0",
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/smithy-client": "^4.4.6",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/url-parser": "^4.0.4",
|
||||||
|
"@smithy/util-base64": "^4.0.0",
|
||||||
|
"@smithy/util-body-length-browser": "^4.0.0",
|
||||||
|
"@smithy/util-body-length-node": "^4.0.0",
|
||||||
|
"@smithy/util-defaults-mode-browser": "^4.0.22",
|
||||||
|
"@smithy/util-defaults-mode-node": "^4.0.22",
|
||||||
|
"@smithy/util-endpoints": "^3.0.6",
|
||||||
|
"@smithy/util-middleware": "^4.0.4",
|
||||||
|
"@smithy/util-retry": "^4.0.6",
|
||||||
|
"@smithy/util-utf8": "^4.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/region-config-resolver": {
|
||||||
|
"version": "3.840.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.840.0.tgz",
|
||||||
|
"integrity": "sha512-Qjnxd/yDv9KpIMWr90ZDPtRj0v75AqGC92Lm9+oHXZ8p1MjG5JE2CW0HL8JRgK9iKzgKBL7pPQRXI8FkvEVfrA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/node-config-provider": "^4.1.3",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/util-config-provider": "^4.0.0",
|
||||||
|
"@smithy/util-middleware": "^4.0.4",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/s3-request-presigner": {
|
||||||
|
"version": "3.844.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.844.0.tgz",
|
||||||
|
"integrity": "sha512-i953TKW1rXbd9G2xEgWoJZDoF0Z1ONRlrXkOKDGOrY/uQhAIPNDz5k6tFcXG5oIaLWW197ShENv3CeEJnhfh3g==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/signature-v4-multi-region": "3.844.0",
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@aws-sdk/util-format-url": "3.840.0",
|
||||||
|
"@smithy/middleware-endpoint": "^4.1.14",
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/smithy-client": "^4.4.6",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/signature-v4-multi-region": {
|
||||||
|
"version": "3.844.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.844.0.tgz",
|
||||||
|
"integrity": "sha512-QC8nocQcZ3Bj7vTnuL47iNhcuUjMC46E2L85mU+sPQo3LN2qBVGSOTF+xSWGvmSFDpkN4ZXUMVeA0cJoJFEDFA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/middleware-sdk-s3": "3.844.0",
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/signature-v4": "^5.1.2",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/token-providers": {
|
||||||
|
"version": "3.844.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.844.0.tgz",
|
||||||
|
"integrity": "sha512-Kh728FEny0fil+LeH8U1offPJCTd/EDh8liBAvLtViLHt2WoX2xC8rk98D38Q5p79aIUhHb3Pf4n9IZfTu/Kog==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/core": "3.844.0",
|
||||||
|
"@aws-sdk/nested-clients": "3.844.0",
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/property-provider": "^4.0.4",
|
||||||
|
"@smithy/shared-ini-file-loader": "^4.0.4",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/types": {
|
||||||
|
"version": "3.840.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz",
|
||||||
|
"integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/util-arn-parser": {
|
||||||
|
"version": "3.804.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.804.0.tgz",
|
||||||
|
"integrity": "sha512-wmBJqn1DRXnZu3b4EkE6CWnoWMo1ZMvlfkqU5zPz67xx1GMaXlDCchFvKAXMjk4jn/L1O3tKnoFDNsoLV1kgNQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/util-endpoints": {
|
||||||
|
"version": "3.844.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.844.0.tgz",
|
||||||
|
"integrity": "sha512-1DHh0WTUmxlysz3EereHKtKoxVUG9UC5BsfAw6Bm4/6qDlJiqtY3oa2vebkYN23yltKdfsCK65cwnBRU59mWVg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/url-parser": "^4.0.4",
|
||||||
|
"@smithy/util-endpoints": "^3.0.6",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/util-format-url": {
|
||||||
|
"version": "3.840.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.840.0.tgz",
|
||||||
|
"integrity": "sha512-VB1PWyI1TQPiPvg4w7tgUGGQER1xxXPNUqfh3baxUSFi1Oh8wHrDnFywkxLm3NMmgDmnLnSZ5Q326qAoyqKLSg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/querystring-builder": "^4.0.4",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/util-locate-window": {
|
||||||
|
"version": "3.804.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.804.0.tgz",
|
||||||
|
"integrity": "sha512-zVoRfpmBVPodYlnMjgVjfGoEZagyRF5IPn3Uo6ZvOZp24chnW/FRstH7ESDHDDRga4z3V+ElUQHKpFDXWyBW5A==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/util-user-agent-browser": {
|
||||||
|
"version": "3.840.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.840.0.tgz",
|
||||||
|
"integrity": "sha512-JdyZM3EhhL4PqwFpttZu1afDpPJCCc3eyZOLi+srpX11LsGj6sThf47TYQN75HT1CarZ7cCdQHGzP2uy3/xHfQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"bowser": "^2.11.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/util-user-agent-node": {
|
||||||
|
"version": "3.844.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.844.0.tgz",
|
||||||
|
"integrity": "sha512-0eTpURp9Gxbyyeqr78ogARZMSWS5KUMZuN+XMHxNpQLmn2S+J3g+MAyoklCcwhKXlbdQq2aMULEiy0mqIWytuw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/middleware-user-agent": "3.844.0",
|
||||||
|
"@aws-sdk/types": "3.840.0",
|
||||||
|
"@smithy/node-config-provider": "^4.1.3",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"aws-crt": ">=1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"aws-crt": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-sdk/xml-builder": {
|
||||||
|
"version": "3.821.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.821.0.tgz",
|
||||||
|
"integrity": "sha512-DIIotRnefVL6DiaHtO6/21DhJ4JZnnIwdNbpwiAhdt/AVbttcE4yw925gsjur0OGv5BTYXQXU3YnANBYnZjuQA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@emnapi/core": {
|
"node_modules/@emnapi/core": {
|
||||||
"version": "1.4.4",
|
"version": "1.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.4.tgz",
|
||||||
@ -2365,6 +3263,725 @@
|
|||||||
"ui": "dist/index.js"
|
"ui": "dist/index.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@smithy/abort-controller": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/chunked-blob-reader": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/chunked-blob-reader-native": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/util-base64": "^4.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/config-resolver": {
|
||||||
|
"version": "4.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz",
|
||||||
|
"integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/node-config-provider": "^4.1.3",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/util-config-provider": "^4.0.0",
|
||||||
|
"@smithy/util-middleware": "^4.0.4",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/core": {
|
||||||
|
"version": "3.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.0.tgz",
|
||||||
|
"integrity": "sha512-7ov8hu/4j0uPZv8b27oeOFtIBtlFmM3ibrPv/Omx1uUdoXvcpJ00U+H/OWWC/keAguLlcqwtyL2/jTlSnApgNQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/middleware-serde": "^4.0.8",
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/util-base64": "^4.0.0",
|
||||||
|
"@smithy/util-body-length-browser": "^4.0.0",
|
||||||
|
"@smithy/util-middleware": "^4.0.4",
|
||||||
|
"@smithy/util-stream": "^4.2.3",
|
||||||
|
"@smithy/util-utf8": "^4.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/credential-provider-imds": {
|
||||||
|
"version": "4.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz",
|
||||||
|
"integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/node-config-provider": "^4.1.3",
|
||||||
|
"@smithy/property-provider": "^4.0.4",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/url-parser": "^4.0.4",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/eventstream-codec": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-7XoWfZqWb/QoR/rAU4VSi0mWnO2vu9/ltS6JZ5ZSZv0eovLVfDfu0/AX4ub33RsJTOth3TiFWSHS5YdztvFnig==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-crypto/crc32": "5.2.0",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/util-hex-encoding": "^4.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/eventstream-serde-browser": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-3fb/9SYaYqbpy/z/H3yIi0bYKyAa89y6xPmIqwr2vQiUT2St+avRt8UKwsWt9fEdEasc5d/V+QjrviRaX1JRFA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/eventstream-serde-universal": "^4.0.4",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/eventstream-serde-config-resolver": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-JGtambizrWP50xHgbzZI04IWU7LdI0nh/wGbqH3sJesYToMi2j/DcoElqyOcqEIG/D4tNyxgRuaqBXWE3zOFhQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/eventstream-serde-node": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-RD6UwNZ5zISpOWPuhVgRz60GkSIp0dy1fuZmj4RYmqLVRtejFqQ16WmfYDdoSoAjlp1LX+FnZo+/hkdmyyGZ1w==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/eventstream-serde-universal": "^4.0.4",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/eventstream-serde-universal": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-UeJpOmLGhq1SLox79QWw/0n2PFX+oPRE1ZyRMxPIaFEfCqWaqpB7BU9C8kpPOGEhLF7AwEqfFbtwNxGy4ReENA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/eventstream-codec": "^4.0.4",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/fetch-http-handler": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-mADw7MS0bYe2OGKkHYMaqarOXuDwRbO6ArD91XhHcl2ynjGCFF+hvqf0LyQcYxkA1zaWjefSkU7Ne9mqgApSgQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/querystring-builder": "^4.0.4",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/util-base64": "^4.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/hash-blob-browser": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-WszRiACJiQV3QG6XMV44i5YWlkrlsM5Yxgz4jvsksuu7LDXA6wAtypfPajtNTadzpJy3KyJPoWehYpmZGKUFIQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/chunked-blob-reader": "^5.0.0",
|
||||||
|
"@smithy/chunked-blob-reader-native": "^4.0.0",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/hash-node": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/util-buffer-from": "^4.0.0",
|
||||||
|
"@smithy/util-utf8": "^4.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/hash-stream-node": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-wHo0d8GXyVmpmMh/qOR0R7Y46/G1y6OR8U+bSTB4ppEzRxd1xVAQ9xOE9hOc0bSjhz0ujCPAbfNLkLrpa6cevg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/util-utf8": "^4.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/invalid-dependency": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/is-array-buffer": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/md5-js": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-uGLBVqcOwrLvGh/v/jw423yWHq/ofUGK1W31M2TNspLQbUV1Va0F5kTxtirkoHawODAZcjXTSGi7JwbnPcDPJg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/util-utf8": "^4.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/middleware-content-length": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/middleware-endpoint": {
|
||||||
|
"version": "4.1.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.14.tgz",
|
||||||
|
"integrity": "sha512-+BGLpK5D93gCcSEceaaYhUD/+OCGXM1IDaq/jKUQ+ujB0PTWlWN85noodKw/IPFZhIKFCNEe19PGd/reUMeLSQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/core": "^3.7.0",
|
||||||
|
"@smithy/middleware-serde": "^4.0.8",
|
||||||
|
"@smithy/node-config-provider": "^4.1.3",
|
||||||
|
"@smithy/shared-ini-file-loader": "^4.0.4",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/url-parser": "^4.0.4",
|
||||||
|
"@smithy/util-middleware": "^4.0.4",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/middleware-retry": {
|
||||||
|
"version": "4.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.15.tgz",
|
||||||
|
"integrity": "sha512-iKYUJpiyTQ33U2KlOZeUb0GwtzWR3C0soYcKuCnTmJrvt6XwTPQZhMfsjJZNw7PpQ3TU4Ati1qLSrkSJxnnSMQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/node-config-provider": "^4.1.3",
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/service-error-classification": "^4.0.6",
|
||||||
|
"@smithy/smithy-client": "^4.4.6",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/util-middleware": "^4.0.4",
|
||||||
|
"@smithy/util-retry": "^4.0.6",
|
||||||
|
"tslib": "^2.6.2",
|
||||||
|
"uuid": "^9.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/middleware-serde": {
|
||||||
|
"version": "4.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz",
|
||||||
|
"integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/middleware-stack": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/node-config-provider": {
|
||||||
|
"version": "4.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz",
|
||||||
|
"integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/property-provider": "^4.0.4",
|
||||||
|
"@smithy/shared-ini-file-loader": "^4.0.4",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/node-http-handler": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-vqfSiHz2v8b3TTTrdXi03vNz1KLYYS3bhHCDv36FYDqxT7jvTll1mMnCrkD+gOvgwybuunh/2VmvOMqwBegxEg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/abort-controller": "^4.0.4",
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/querystring-builder": "^4.0.4",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/property-provider": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/protocol-http": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/querystring-builder": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/util-uri-escape": "^4.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/querystring-parser": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/service-error-classification": {
|
||||||
|
"version": "4.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.6.tgz",
|
||||||
|
"integrity": "sha512-RRoTDL//7xi4tn5FrN2NzH17jbgmnKidUqd4KvquT0954/i6CXXkh1884jBiunq24g9cGtPBEXlU40W6EpNOOg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/types": "^4.3.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/shared-ini-file-loader": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/signature-v4": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/is-array-buffer": "^4.0.0",
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/util-hex-encoding": "^4.0.0",
|
||||||
|
"@smithy/util-middleware": "^4.0.4",
|
||||||
|
"@smithy/util-uri-escape": "^4.0.0",
|
||||||
|
"@smithy/util-utf8": "^4.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/smithy-client": {
|
||||||
|
"version": "4.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.6.tgz",
|
||||||
|
"integrity": "sha512-3wfhywdzB/CFszP6moa5L3lf5/zSfQoH0kvVSdkyK2az5qZet0sn2PAHjcTDiq296Y4RP5yxF7B6S6+3oeBUCQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/core": "^3.7.0",
|
||||||
|
"@smithy/middleware-endpoint": "^4.1.14",
|
||||||
|
"@smithy/middleware-stack": "^4.0.4",
|
||||||
|
"@smithy/protocol-http": "^5.1.2",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/util-stream": "^4.2.3",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/types": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/url-parser": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/querystring-parser": "^4.0.4",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/util-base64": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/util-buffer-from": "^4.0.0",
|
||||||
|
"@smithy/util-utf8": "^4.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/util-body-length-browser": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/util-body-length-node": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/util-buffer-from": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/is-array-buffer": "^4.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/util-config-provider": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/util-defaults-mode-browser": {
|
||||||
|
"version": "4.0.22",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.22.tgz",
|
||||||
|
"integrity": "sha512-hjElSW18Wq3fUAWVk6nbk7pGrV7ZT14DL1IUobmqhV3lxcsIenr5FUsDe2jlTVaS8OYBI3x+Og9URv5YcKb5QA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/property-provider": "^4.0.4",
|
||||||
|
"@smithy/smithy-client": "^4.4.6",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"bowser": "^2.11.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/util-defaults-mode-node": {
|
||||||
|
"version": "4.0.22",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.22.tgz",
|
||||||
|
"integrity": "sha512-7B8mfQBtwwr2aNRRmU39k/bsRtv9B6/1mTMrGmmdJFKmLAH+KgIiOuhaqfKOBGh9sZ/VkZxbvm94rI4MMYpFjQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/config-resolver": "^4.1.4",
|
||||||
|
"@smithy/credential-provider-imds": "^4.0.6",
|
||||||
|
"@smithy/node-config-provider": "^4.1.3",
|
||||||
|
"@smithy/property-provider": "^4.0.4",
|
||||||
|
"@smithy/smithy-client": "^4.4.6",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/util-endpoints": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/node-config-provider": "^4.1.3",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/util-hex-encoding": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/util-middleware": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/util-retry": {
|
||||||
|
"version": "4.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.6.tgz",
|
||||||
|
"integrity": "sha512-+YekoF2CaSMv6zKrA6iI/N9yva3Gzn4L6n35Luydweu5MMPYpiGZlWqehPHDHyNbnyaYlz/WJyYAZnC+loBDZg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/service-error-classification": "^4.0.6",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/util-stream": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-cQn412DWHHFNKrQfbHY8vSFI3nTROY1aIKji9N0tpp8gUABRilr7wdf8fqBbSlXresobM+tQFNk6I+0LXK/YZg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/fetch-http-handler": "^5.1.0",
|
||||||
|
"@smithy/node-http-handler": "^4.1.0",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"@smithy/util-base64": "^4.0.0",
|
||||||
|
"@smithy/util-buffer-from": "^4.0.0",
|
||||||
|
"@smithy/util-hex-encoding": "^4.0.0",
|
||||||
|
"@smithy/util-utf8": "^4.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/util-uri-escape": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/util-utf8": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/util-buffer-from": "^4.0.0",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@smithy/util-waiter": {
|
||||||
|
"version": "4.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.0.6.tgz",
|
||||||
|
"integrity": "sha512-slcr1wdRbX7NFphXZOxtxRNA7hXAAtJAXJDE/wdoMAos27SIquVCKiSqfB6/28YzQ8FCsB5NKkhdM5gMADbqxg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@smithy/abort-controller": "^4.0.4",
|
||||||
|
"@smithy/types": "^4.3.1",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@swc/counter": {
|
"node_modules/@swc/counter": {
|
||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||||
@ -2405,6 +4022,25 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/body-parser": {
|
||||||
|
"version": "1.19.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
||||||
|
"integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/connect": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/connect": {
|
||||||
|
"version": "3.4.38",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
||||||
|
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/estree": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||||
@ -2412,6 +4048,35 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/express": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/body-parser": "*",
|
||||||
|
"@types/express-serve-static-core": "^5.0.0",
|
||||||
|
"@types/serve-static": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/express-serve-static-core": {
|
||||||
|
"version": "5.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz",
|
||||||
|
"integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*",
|
||||||
|
"@types/qs": "*",
|
||||||
|
"@types/range-parser": "*",
|
||||||
|
"@types/send": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/http-errors": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
@ -2437,6 +4102,12 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/mime": {
|
||||||
|
"version": "1.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
|
||||||
|
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/ms": {
|
"node_modules/@types/ms": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
|
||||||
@ -2444,16 +4115,36 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/multer": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/express": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.19.6",
|
"version": "20.19.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.6.tgz",
|
||||||
"integrity": "sha512-uYssdp9z5zH5GQ0L4zEJ2ZuavYsJwkozjiUzCRfGtaaQcyjAMJ34aP8idv61QlqTozu6kudyr6JMq9Chf09dfA==",
|
"integrity": "sha512-uYssdp9z5zH5GQ0L4zEJ2ZuavYsJwkozjiUzCRfGtaaQcyjAMJ34aP8idv61QlqTozu6kudyr6JMq9Chf09dfA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/qs": {
|
||||||
|
"version": "6.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
|
||||||
|
"integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/range-parser": {
|
||||||
|
"version": "1.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
|
||||||
|
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.1.8",
|
"version": "19.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz",
|
||||||
@ -2484,6 +4175,33 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/send": {
|
||||||
|
"version": "0.17.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz",
|
||||||
|
"integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/mime": "^1",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/serve-static": {
|
||||||
|
"version": "1.15.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz",
|
||||||
|
"integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/http-errors": "*",
|
||||||
|
"@types/node": "*",
|
||||||
|
"@types/send": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/uuid": {
|
||||||
|
"version": "9.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz",
|
||||||
|
"integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.36.0",
|
"version": "8.36.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.36.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.36.0.tgz",
|
||||||
@ -3160,6 +4878,12 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/append-field": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/arg": {
|
"node_modules/arg": {
|
||||||
"version": "5.0.2",
|
"version": "5.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||||
@ -3396,7 +5120,6 @@
|
|||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||||
"integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
|
"integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"possible-typed-array-names": "^1.0.0"
|
"possible-typed-array-names": "^1.0.0"
|
||||||
@ -3408,6 +5131,66 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/aws-sdk": {
|
||||||
|
"version": "2.1692.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1692.0.tgz",
|
||||||
|
"integrity": "sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer": "4.9.2",
|
||||||
|
"events": "1.1.1",
|
||||||
|
"ieee754": "1.1.13",
|
||||||
|
"jmespath": "0.16.0",
|
||||||
|
"querystring": "0.2.0",
|
||||||
|
"sax": "1.2.1",
|
||||||
|
"url": "0.10.3",
|
||||||
|
"util": "^0.12.4",
|
||||||
|
"uuid": "8.0.0",
|
||||||
|
"xml2js": "0.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/aws-sdk/node_modules/buffer": {
|
||||||
|
"version": "4.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
|
||||||
|
"integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"base64-js": "^1.0.2",
|
||||||
|
"ieee754": "^1.1.4",
|
||||||
|
"isarray": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/aws-sdk/node_modules/ieee754": {
|
||||||
|
"version": "1.1.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
|
||||||
|
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/aws-sdk/node_modules/isarray": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/aws-sdk/node_modules/sax": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/aws-sdk/node_modules/uuid": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/axe-core": {
|
"node_modules/axe-core": {
|
||||||
"version": "4.10.3",
|
"version": "4.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz",
|
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz",
|
||||||
@ -3513,6 +5296,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/bowser": {
|
||||||
|
"version": "2.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz",
|
||||||
|
"integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.12",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
@ -3570,7 +5359,6 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/busboy": {
|
"node_modules/busboy": {
|
||||||
@ -3588,7 +5376,6 @@
|
|||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
|
||||||
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
|
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.0",
|
"call-bind-apply-helpers": "^1.0.0",
|
||||||
@ -3607,7 +5394,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
@ -3621,7 +5407,6 @@
|
|||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
||||||
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.2",
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
@ -3892,6 +5677,21 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/concat-stream": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
|
||||||
|
"engines": [
|
||||||
|
"node >= 6.0"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-from": "^1.0.0",
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"readable-stream": "^3.0.2",
|
||||||
|
"typedarray": "^0.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/croner": {
|
"node_modules/croner": {
|
||||||
"version": "4.1.97",
|
"version": "4.1.97",
|
||||||
"resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz",
|
"resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz",
|
||||||
@ -4057,7 +5857,6 @@
|
|||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-define-property": "^1.0.0",
|
"es-define-property": "^1.0.0",
|
||||||
@ -4145,11 +5944,22 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dotenv": {
|
||||||
|
"version": "17.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.0.tgz",
|
||||||
|
"integrity": "sha512-Q4sgBT60gzd0BB0lSyYD3xM4YxrXA9y4uBDof1JNYGzOXrQdQ6yX+7XIAqoFOGQFOTK1D3Hts5OllpxMDZFONQ==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://dotenvx.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dunder-proto": {
|
"node_modules/dunder-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.1",
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
@ -4267,7 +6077,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@ -4277,7 +6086,6 @@
|
|||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@ -4315,7 +6123,6 @@
|
|||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0"
|
"es-errors": "^1.3.0"
|
||||||
@ -4928,6 +6735,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/events": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/execa": {
|
"node_modules/execa": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz",
|
||||||
@ -5019,6 +6835,24 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-xml-parser": {
|
||||||
|
"version": "5.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz",
|
||||||
|
"integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"strnum": "^2.1.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"fxparser": "src/cli/cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fastq": {
|
"node_modules/fastq": {
|
||||||
"version": "1.19.1",
|
"version": "1.19.1",
|
||||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
|
||||||
@ -5146,7 +6980,6 @@
|
|||||||
"version": "0.3.5",
|
"version": "0.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
||||||
"integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
|
"integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-callable": "^1.2.7"
|
"is-callable": "^1.2.7"
|
||||||
@ -5297,7 +7130,6 @@
|
|||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.2",
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
@ -5331,7 +7163,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dunder-proto": "^1.0.1",
|
"dunder-proto": "^1.0.1",
|
||||||
@ -5513,7 +7344,6 @@
|
|||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@ -5562,7 +7392,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-define-property": "^1.0.0"
|
"es-define-property": "^1.0.0"
|
||||||
@ -5591,7 +7420,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@ -5604,7 +7432,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-symbols": "^1.0.3"
|
"has-symbols": "^1.0.3"
|
||||||
@ -5793,6 +7620,22 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/is-arguments": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bound": "^1.0.2",
|
||||||
|
"has-tostringtag": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-array-buffer": {
|
"node_modules/is-array-buffer": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
|
||||||
@ -5897,7 +7740,6 @@
|
|||||||
"version": "1.2.7",
|
"version": "1.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
|
||||||
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
|
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@ -5994,7 +7836,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz",
|
||||||
"integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==",
|
"integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.3",
|
"call-bound": "^1.0.3",
|
||||||
@ -6089,7 +7930,6 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
||||||
"integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
|
"integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.2",
|
"call-bound": "^1.0.2",
|
||||||
@ -6184,7 +8024,6 @@
|
|||||||
"version": "1.1.15",
|
"version": "1.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
|
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
|
||||||
"integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
|
"integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"which-typed-array": "^1.1.16"
|
"which-typed-array": "^1.1.16"
|
||||||
@ -6309,6 +8148,15 @@
|
|||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jmespath": {
|
||||||
|
"version": "0.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz",
|
||||||
|
"integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/js-git": {
|
"node_modules/js-git": {
|
||||||
"version": "0.7.8",
|
"version": "0.7.8",
|
||||||
"resolved": "https://registry.npmjs.org/js-git/-/js-git-0.7.8.tgz",
|
"resolved": "https://registry.npmjs.org/js-git/-/js-git-0.7.8.tgz",
|
||||||
@ -6665,12 +8513,20 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/media-typer": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/merge-stream": {
|
"node_modules/merge-stream": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||||
@ -6699,6 +8555,27 @@
|
|||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mimic-fn": {
|
"node_modules/mimic-fn": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
||||||
@ -6728,7 +8605,6 @@
|
|||||||
"version": "1.2.8",
|
"version": "1.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
@ -6784,6 +8660,36 @@
|
|||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/multer": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/multer/-/multer-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-Ug8bXeTIUlxurg8xLTEskKShvcKDZALo1THEX5E41pYCD2sCVub5/kIRIGqWNoqV6szyLyQKV6mD4QUrWE5GCQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"append-field": "^1.0.0",
|
||||||
|
"busboy": "^1.6.0",
|
||||||
|
"concat-stream": "^2.0.0",
|
||||||
|
"mkdirp": "^0.5.6",
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"type-is": "^1.6.18",
|
||||||
|
"xtend": "^4.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/multer/node_modules/mkdirp": {
|
||||||
|
"version": "0.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||||
|
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"minimist": "^1.2.6"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"mkdirp": "bin/cmd.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mute-stream": {
|
"node_modules/mute-stream": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
|
||||||
@ -7624,7 +9530,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||||
"integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
|
"integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@ -7846,6 +9751,15 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/querystring": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==",
|
||||||
|
"deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/queue-microtask": {
|
"node_modules/queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
@ -8279,7 +10193,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
|
||||||
"integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
|
"integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.2",
|
"call-bound": "^1.0.2",
|
||||||
@ -8304,7 +10217,6 @@
|
|||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
||||||
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
|
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/scheduler": {
|
"node_modules/scheduler": {
|
||||||
@ -8329,7 +10241,6 @@
|
|||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||||
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"define-data-property": "^1.1.4",
|
"define-data-property": "^1.1.4",
|
||||||
@ -8917,6 +10828,18 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/strnum": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/styled-jsx": {
|
"node_modules/styled-jsx": {
|
||||||
"version": "5.1.6",
|
"version": "5.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
||||||
@ -9322,6 +11245,19 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/type-is": {
|
||||||
|
"version": "1.6.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||||
|
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"media-typer": "0.3.0",
|
||||||
|
"mime-types": "~2.1.24"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/typed-array-buffer": {
|
"node_modules/typed-array-buffer": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
|
||||||
@ -9400,6 +11336,12 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typedarray": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.8.3",
|
"version": "5.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||||
@ -9437,7 +11379,6 @@
|
|||||||
"version": "6.21.0",
|
"version": "6.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/universalify": {
|
"node_modules/universalify": {
|
||||||
@ -9494,6 +11435,22 @@
|
|||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/url": {
|
||||||
|
"version": "0.10.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz",
|
||||||
|
"integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"punycode": "1.3.2",
|
||||||
|
"querystring": "0.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/url/node_modules/punycode": {
|
||||||
|
"version": "1.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
|
||||||
|
"integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/use-callback-ref": {
|
"node_modules/use-callback-ref": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
|
||||||
@ -9537,12 +11494,38 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/util": {
|
||||||
|
"version": "0.12.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
|
||||||
|
"integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"is-arguments": "^1.0.4",
|
||||||
|
"is-generator-function": "^1.0.7",
|
||||||
|
"is-typed-array": "^1.1.3",
|
||||||
|
"which-typed-array": "^1.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/util-deprecate": {
|
"node_modules/util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vizion": {
|
"node_modules/vizion": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/vizion/-/vizion-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/vizion/-/vizion-2.2.1.tgz",
|
||||||
@ -9682,7 +11665,6 @@
|
|||||||
"version": "1.1.19",
|
"version": "1.1.19",
|
||||||
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz",
|
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz",
|
||||||
"integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==",
|
"integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"available-typed-arrays": "^1.0.7",
|
"available-typed-arrays": "^1.0.7",
|
||||||
@ -9823,6 +11805,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/xml2js": {
|
||||||
|
"version": "0.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
|
||||||
|
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"sax": ">=0.6.0",
|
||||||
|
"xmlbuilder": "~11.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/xmlbuilder": {
|
||||||
|
"version": "11.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||||
|
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/xtend": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
"db:migrate": "prisma migrate dev",
|
"db:migrate": "prisma migrate dev",
|
||||||
"db:studio": "prisma studio",
|
"db:studio": "prisma studio",
|
||||||
"db:seed": "tsx scripts/seed.ts",
|
"db:seed": "tsx scripts/seed.ts",
|
||||||
|
"migrate:images": "tsx scripts/migrate-images-to-s3.ts",
|
||||||
"pm2:start": "pm2 start ecosystem.config.js",
|
"pm2:start": "pm2 start ecosystem.config.js",
|
||||||
"pm2:stop": "pm2 stop ecosystem.config.js",
|
"pm2:stop": "pm2 stop ecosystem.config.js",
|
||||||
"pm2:restart": "pm2 restart ecosystem.config.js",
|
"pm2:restart": "pm2 restart ecosystem.config.js",
|
||||||
@ -21,16 +22,22 @@
|
|||||||
"seed": "tsx scripts/seed.ts"
|
"seed": "tsx scripts/seed.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-s3": "^3.844.0",
|
||||||
|
"@aws-sdk/s3-request-presigner": "^3.844.0",
|
||||||
"@prisma/client": "^6.11.1",
|
"@prisma/client": "^6.11.1",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
||||||
"@radix-ui/react-slot": "^1.1.2",
|
"@radix-ui/react-slot": "^1.1.2",
|
||||||
"@shadcn/ui": "^0.0.4",
|
"@shadcn/ui": "^0.0.4",
|
||||||
|
"@types/multer": "^2.0.0",
|
||||||
|
"aws-sdk": "^2.1692.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"dotenv": "^17.2.0",
|
||||||
"framer-motion": "^12.3.1",
|
"framer-motion": "^12.3.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"lucide-react": "^0.474.0",
|
"lucide-react": "^0.474.0",
|
||||||
|
"multer": "^2.0.1",
|
||||||
"next": "^15.3.1",
|
"next": "^15.3.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
@ -6,7 +6,7 @@ generator client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
provider = "sqlite"
|
provider = "postgresql"
|
||||||
url = env("DATABASE_URL")
|
url = env("DATABASE_URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
93
scripts/migrate-images-to-s3.ts
Normal file
93
scripts/migrate-images-to-s3.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { uploadFileToS3 } from '../lib/s3';
|
||||||
|
|
||||||
|
async function migrateImagesToS3() {
|
||||||
|
const publicDir = path.join(process.cwd(), 'public');
|
||||||
|
const imageDirs = [
|
||||||
|
'images/certificates',
|
||||||
|
'images/placeholders/services',
|
||||||
|
'images'
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log('Начинаем миграцию изображений в S3...');
|
||||||
|
|
||||||
|
for (const dir of imageDirs) {
|
||||||
|
const fullPath = path.join(publicDir, dir);
|
||||||
|
|
||||||
|
if (!fs.existsSync(fullPath)) {
|
||||||
|
console.log(`Папка ${dir} не найдена, пропускаем...`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = fs.readdirSync(fullPath);
|
||||||
|
const imageFiles = files.filter(file =>
|
||||||
|
/\.(jpg|jpeg|png|gif|webp|svg)$/i.test(file)
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`Найдено ${imageFiles.length} изображений в папке ${dir}`);
|
||||||
|
|
||||||
|
for (const file of imageFiles) {
|
||||||
|
try {
|
||||||
|
const filePath = path.join(fullPath, file);
|
||||||
|
const fileBuffer = fs.readFileSync(filePath);
|
||||||
|
const stats = fs.statSync(filePath);
|
||||||
|
|
||||||
|
// Определяем MIME тип
|
||||||
|
const ext = path.extname(file).toLowerCase();
|
||||||
|
let contentType = 'image/jpeg';
|
||||||
|
|
||||||
|
switch (ext) {
|
||||||
|
case '.png':
|
||||||
|
contentType = 'image/png';
|
||||||
|
break;
|
||||||
|
case '.gif':
|
||||||
|
contentType = 'image/gif';
|
||||||
|
break;
|
||||||
|
case '.webp':
|
||||||
|
contentType = 'image/webp';
|
||||||
|
break;
|
||||||
|
case '.svg':
|
||||||
|
contentType = 'image/svg+xml';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Загружаем в S3
|
||||||
|
const result = await uploadFileToS3(
|
||||||
|
fileBuffer,
|
||||||
|
contentType,
|
||||||
|
dir.replace('images/', '').replace('images', 'legacy'), // Сохраняем структуру папок
|
||||||
|
file
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`✅ Загружен: ${file} -> ${result.publicUrl}`);
|
||||||
|
|
||||||
|
// Создаем файл с маппингом для обновления ссылок
|
||||||
|
const mappingFile = path.join(process.cwd(), 'image-mapping.json');
|
||||||
|
let mapping = {};
|
||||||
|
|
||||||
|
if (fs.existsSync(mappingFile)) {
|
||||||
|
mapping = JSON.parse(fs.readFileSync(mappingFile, 'utf8'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldUrl = `/${dir}/${file}`;
|
||||||
|
mapping[oldUrl] = result.publicUrl;
|
||||||
|
|
||||||
|
fs.writeFileSync(mappingFile, JSON.stringify(mapping, null, 2));
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Ошибка при загрузке ${file}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Миграция завершена!');
|
||||||
|
console.log('Создан файл image-mapping.json с соответствием старых и новых URL');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запускаем миграцию, если скрипт вызван напрямую
|
||||||
|
if (require.main === module) {
|
||||||
|
migrateImagesToS3().catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default migrateImagesToS3;
|
29
stack.env
Normal file
29
stack.env
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# ====================================================================
|
||||||
|
# STACK ENVIRONMENT VARIABLES FOR DEPLOYMENT
|
||||||
|
# ====================================================================
|
||||||
|
|
||||||
|
# Port configuration
|
||||||
|
PORT=3006
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
NODE_ENV=production
|
||||||
|
|
||||||
|
# Database configuration
|
||||||
|
DATABASE_URL=postgresql://ckeproekt:y0i_-yCfeL@85.234.110.60:5432/ckeproekt
|
||||||
|
|
||||||
|
# Authentication & Security
|
||||||
|
NEXTAUTH_SECRET=15455c6e0f1e5aa95945e23a591acbd0baf11c1589e08c36d95afefa496e6a64510ee98dbf9a4afade12bec2cb3a4d1e0e9b238048c8cef927c9ce568ea4a0a7
|
||||||
|
|
||||||
|
# S3 Storage Configuration
|
||||||
|
S3_ENDPOINT=https://s3.twcstorage.ru
|
||||||
|
S3_REGION=ru-1
|
||||||
|
S3_ACCESS_KEY_ID=I6XD2OR7YO2ZN6L6Z629
|
||||||
|
S3_SECRET_ACCESS_KEY=9xCOoafisG0aB9lJNvdLO1UuK73fBvMcpHMdijrJ
|
||||||
|
S3_BUCKET_NAME=617774af-ckeproekt
|
||||||
|
|
||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_BOT_TOKEN=7802786776:AAGQGYf2BxuBFmZOhRcLTX52KnUEYifTKZY
|
||||||
|
TELEGRAM_CHAT_ID=-1002321880357
|
||||||
|
|
||||||
|
# Next.js URL (update with your domain)
|
||||||
|
NEXTAUTH_URL=https://ckeproekt.ru
|
Reference in New Issue
Block a user