upd
This commit is contained in:
131
README.md
Normal file
131
README.md
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
# 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
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user