# TMC Test Task Fullstack-приложение для управления коллекцией элементов с двухпанельным интерфейсом, виртуальным скроллом и drag & drop. **Задеплоенное приложение:** [https://test-1.koptilnya.xyz/](https://test-1.koptilnya.xyz/) --- ## Что это Интерфейс с двумя панелями: слева — 1 000 000 предсгенерированных элементов, справа — выбранные. Элементы можно перемещать между панелями, искать, добавлять новые и менять порядок через drag & drop. --- ## Стек | Слой | Технологии | |---|---| | Backend | Node.js 22, Express 4, TypeScript 5 | | Frontend | Nuxt 4 (SPA), Vue 3, TypeScript 6 | | UI | @nuxt/ui, vue-draggable-next, SCSS | | Инфраструктура | Docker, Traefik, Let's Encrypt | | CI/CD | Gitea Actions | --- ## Архитектура бэкенда Сервер запускается на порту **1337**. API задокументировано через Swagger: `/api/docs`. ### Эндпоинты (`/api/items`) | Метод | Путь | Описание | |---|---|---| | `GET` | `/` | Невыбранные элементы (пагинация, поиск) | | `GET` | `/selected` | Выбранные элементы в заданном порядке | | `POST` | `/add` | Добавить элемент в очередь на создание | | `POST` | `/add-value` | Создать элемент с произвольным значением | | `POST` | `/select` | Переместить элемент в правую панель | | `POST` | `/deselect` | Вернуть элемент в левую панель | | `PUT` | `/reorder` | Изменить порядок выбранного элемента | ### Хранилище In-memory хранилище с 1 000 000 предсгенерированных элементов (случайные строки 6–12 символов). Выбранные элементы хранятся в `Set` для O(1)-поиска и в упорядоченном массиве для drag & drop. Новые элементы получают ID начиная с 1 000 001. ### Очередь с батчингом Ключевая часть бэкенда — класс `RequestQueue` (`src/services/queue.ts`): - **Добавление элементов** — батч сбрасывается каждые **10 секунд** или при достижении **100 операций** - **Select / Deselect** — применяются **немедленно** для мгновенного отклика UI - **Reorder** — батч сбрасывается каждую **1 секунду** - **Дедупликация** — повторные операции над одним элементом отбрасываются Такое разделение позволяет держать UI отзывчивым для частых пользовательских действий и одновременно снижать нагрузку на запись. --- ## Архитектура фронтенда SPA на Nuxt 4, SSR отключён. Сервер отдаётся через Nginx. ### Панели **Левая панель (`LeftPanel.vue`)** — невыбранные элементы: - Виртуальный скролл (высота строки 47px, overscan 5 элементов) - Infinite scroll для подгрузки страниц - Поиск с дебаунсом 300 мс - Модальное окно добавления нового элемента **Правая панель (`RightPanel.vue`)** — выбранные элементы: - Те же параметры виртуализации - Drag & drop через `vue-draggable-next` - Автоскролл во время перетаскивания (зона 80px, скорость до 12px/тик) - После отпускания — синхронизация нового порядка с бэкендом ### Состояние Composable `useItems.ts` управляет всем состоянием: загрузка, пагинация, поиск, операции select/deselect/reorder. API-клиент генерируется автоматически из OpenAPI-спеки бэкенда (`npm run gen:api`). --- ## CI/CD и деплой Пайплайны описаны в `.gitea/workflows/` и запускаются при пуше в `main`. ### deploy-backend.yml Триггер: изменения в `/backend/**` 1. Checkout через SSH 2. `docker build -t tmc-backend ./backend` 3. Остановка старого контейнера 4. Запуск нового в сети `traefik` с env `PATH_PREFIX=/test-1` 5. Traefik автоматически получает Let's Encrypt сертификат и проксирует на `api.koptilnya.xyz` ### deploy-frontend.yml Триггер: изменения в `/frontend/**` 1. Checkout через SSH 2. `docker build` с build-arg `API_BASE_URL` (URL бэкенда) 3. Остановка старого контейнера 4. Запуск нового; Traefik проксирует на `test-1.koptilnya.xyz` Оба сервиса живут в одной Docker-сети `traefik`. Traefik роутит запросы по хосту и управляет TLS. --- ## Локальный запуск ```bash # Backend cd backend pnpm install pnpm dev # :1337 # Frontend cd frontend pnpm install pnpm dev # :3000 ``` По умолчанию фронт обращается к `http://localhost:1337`. Переопределить через `NUXT_PUBLIC_API_BASE`. ```bash # Обновить API-клиент после изменений в бэкенде cd frontend pnpm gen:api # требует запущенного бэкенда на :1337 ```