132 lines
6.0 KiB
Markdown
132 lines
6.0 KiB
Markdown
# 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
|
||
```
|