No description
  • TypeScript 85.9%
  • CSS 10.7%
  • JavaScript 1.6%
  • Dockerfile 0.9%
  • HTML 0.5%
  • Other 0.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-26 05:21:07 +05:00
.githooks feat(build): номер сборки в build.json с автоинкрементом через git-хук 2026-09-26 05:21:07 +05:00
apps feat(build): номер сборки в build.json с автоинкрементом через git-хук 2026-09-26 05:21:07 +05:00
catalog feat: каталог периодически покупаемых товаров — группы JSON, привязка к списку, подсказки из каталога 2026-09-24 20:42:40 +05:00
packages/shared feat: рецепты — наборы позиций, добавление в список, теги, цвета и комментарии 2026-09-26 04:22:00 +05:00
.dockerignore Initial commit: Vinka - self-hosted family checklist PWA (Fastify+SQLite server, React PWA, shared types) 2026-08-31 18:38:33 +05:00
.gitattributes Initial commit: Vinka - self-hosted family checklist PWA (Fastify+SQLite server, React PWA, shared types) 2026-08-31 18:38:33 +05:00
.gitignore Initial commit: Vinka - self-hosted family checklist PWA (Fastify+SQLite server, React PWA, shared types) 2026-08-31 18:38:33 +05:00
.prettierignore feat(build): номер сборки в build.json с автоинкрементом через git-хук 2026-09-26 05:21:07 +05:00
.prettierrc.json Initial commit: Vinka - self-hosted family checklist PWA (Fastify+SQLite server, React PWA, shared types) 2026-08-31 18:38:33 +05:00
AGENTS.md feat(build): номер сборки в build.json с автоинкрементом через git-хук 2026-09-26 05:21:07 +05:00
build.json feat(build): номер сборки в build.json с автоинкрементом через git-хук 2026-09-26 05:21:07 +05:00
docker-compose.yml feat(build): номер сборки в build.json с автоинкрементом через git-хук 2026-09-26 05:21:07 +05:00
docker-entrypoint.sh fix(docker): entrypoint выставляет владельца ./data (SQLITE_CANTOPEN) 2026-09-25 18:17:07 +05:00
Dockerfile feat(build): номер сборки в build.json с автоинкрементом через git-хук 2026-09-26 05:21:07 +05:00
eslint.config.js Initial commit: Vinka - self-hosted family checklist PWA (Fastify+SQLite server, React PWA, shared types) 2026-08-31 18:38:33 +05:00
package-lock.json chore: переименовать приложение в «Винка — список покупок» 2026-09-25 18:17:11 +05:00
package.json feat(build): номер сборки в build.json с автоинкрементом через git-хук 2026-09-26 05:21:07 +05:00
README.md chore: переименовать приложение в «Винка — список покупок» 2026-09-25 18:17:11 +05:00
tsconfig.base.json Initial commit: Vinka - self-hosted family checklist PWA (Fastify+SQLite server, React PWA, shared types) 2026-08-31 18:38:33 +05:00

Винка — список покупок

Self-hosted PWA «Винка» — совместный список покупок для семьи/соседей. Один Docker-контейнер раздаёт фронтенд (PWA) и бэкенд (REST + WebSocket + SQLite) на одном порту. Все списки доступны каждому, кто открыл главнюю страницу — авторизации и share-ссылок нет.

Запуск за 30 секунд

docker compose up -d --build
# открыть http://localhost:8080

Хост-порт задаётся APP_PORT в .env (APP_PORT=9090 → http://localhost:9090), лимит WS — MAX_WS_CLIENTS. ./catalog смонтирован read-only, поэтому новые группы товаров подхватываются без пересборки образа. Данные — в ./data/lists.db (WAL). Приложение работает под UID 10001; entrypoint при старте сам выставляет владельца ./data (ручной chown не нужен, каталог создаётся автоматически). Бэкап: VACUUM INTO без остановки или остановка + копирование lists.db*.

Переменные окружения:

Переменная По умолчанию Назначение
PORT 8080 HTTP/WS порт
DATA_DIR /data (в контейнере) каталог БД
MAX_WS_CLIENTS 200 лимит WS-подключений
CATALOG_DIR <репозиторий>/catalog каталог JSON-групп периодически покупаемых товаров

HTTPS — на вашем обратном прокси (caddy/traefik/nginx). Пример Caddy:

lists.example.com {
  reverse_proxy localhost:8080
}

PWA ставится на домашний экран (Chrome: «Установить приложение»).

Деплой на сервер (Proxmox + Dockhand)

  1. Виртуальная машина Proxmox: Debian 12/13 (шаблон cloud-init), ~1 vCPU / 1 ГБ / 15 ГБ, мост vmbr0. Docker на сам хост Proxmox не ставят.

  2. Docker Engine в ВМ: curl -fsSL https://get.docker.com | sh, затем usermod -aG docker $USER.

  3. Dockhand (если ещё нет) — с «matching paths», чтобы относительные bind-mount'ы стеков (./data) работали:

    mkdir -p /opt/dockhand
    docker run -d --name dockhand --restart unless-stopped \
      -p 3000:3000 \
      -v /var/run/docker.sock:/var/run/docker.sock \
      -v /opt/dockhand:/opt/dockhand \
      -e DATA_DIR=/opt/dockhand \
      fnsys/dockhand:latest
    

    Открыть http://<vm>:3000 → Settings → Authentication → создать администратора.

  4. Код приложения на сервер: git clone (приватный репозиторий — тогда можно и автодеплой по push через Stacks → Git) или rsync -a --exclude node_modules --exclude dist --exclude data --exclude '*.rar' ./ <user>@<vm>:/opt/stacks/vinka/. Каталог data/ создаётся автоматически, права контейнера (UID 10001) entrypoint выставит при старте.

  5. Stack в Dockhand: Stacks → New Compose Stack → вставить docker-compose.yml (или указать путь к нему) → Deploy — образ соберётся на сервере (первая сборка дольше: нативная компиляция better-sqlite3). Проверка: GET http://<vm>:8080/api/health.

  6. Обновление: git pull + redeploy стека из Dockhand (или docker compose up -d --build). Схема SQLite мигрируется автоматически при старте.

  7. Бэкапы: снапшот/vzdump ВМ + копия data/lists.db* (в Dockhand есть Backups для bind-mount'ов с расписанием; либо cron: rsync каталога data/).

Модель доступа

  • Авторизации нет: любой, кто открыл страницу приложения, видит и может менять все списки. Это осознанное решение для доверенного круга (дом/локальная сеть).
  • Приложение не предназначено для публичного интернета без доступа по VPN/локальной сети.
  • Все участники видят изменения в реальном времени (WebSocket, одно соединение на клиента, события всех списков).
  • Настройки темы/тонирования хранятся локально в браузере (у каждого участника свои).

Архитектура

apps/web      PWA: Vite + React + TypeScript (service worker, manifest)
apps/server   Node 20+ / Fastify + @fastify/websocket + better-sqlite3 (WAL)
packages/shared   общие типы моделей и WS-протокол (v: 1)
  • Бэкенд раздаёт собранные статики PWA (@fastify/static, SPA-fallback) + API/WS на одном порту.
  • Мутации идемпотентны: клиент генерирует UUID; повтор = upsert.
  • Realtime: клиент держит одно WS-соединение и получает события всех списков (list.created/updated/removed, item.added/updated/removed/bought/returned); после восстановления соединения клиент делает полную синхронизацию.
  • Все списки хранятся на сервере и отдают всем; локальный кэш — для офлайн-отображения до синхронизации.

API

GET    /api/health                     liveness
POST   /api/lists                      создать (id/name/color, upsert)
GET    /api/lists                      все списки (доступно каждому)
GET    /api/lists/{id}                 полный снимок списка
PATCH  /api/lists/{id}                 имя / цвет / порядок / группа каталога (catalogGroup, null — отвязать)
DELETE /api/lists/{id}                 удалить (можно удалить все списки)
POST   /api/lists/{id}/items           добавить позицию
PATCH  /api/items/{id}                 text / urgent / comment
DELETE /api/items/{id}                 удалить позицию
POST   /api/items/{id}/buy             в историю (boughtAt ставит сервер)
POST   /api/items/{id}/return          возврат из истории (urgent=false, в конец)
GET    /api/catalog                    группы каталога: id, file, name, count
GET    /api/catalog/{id}               полный список товаров группы
WS     /ws                             события всех списков (одно соединение на клиента)

Каталог товаров

catalog/*.json — группы периодически покупаемых товаров (формат {"name": "...", "items": ["..."]}, id группы = имя файла без .json). В настройках списка (⋯ → «Каталог») можно привязать к списку одну или несколько групп: видно файлы и количество записей, есть просмотр полного списка группы.

Разработка

npm install
# better-sqlite3 нужен собранный native-модуль: если npm не скачал prebuild —
# возьмите build/Release/better_sqlite3.node из релиза WiseLibs/better-sqlite3
# (v{version}-node-v{abi}-{platform}-{arch}.tar.gz) и положите в
# node_modules/better-sqlite3/build/Release/.

npm run build        # shared → server → web
npm start            # собранное приложение: API + PWA на http://localhost:8080
npm test             # vitest: общие пакеты + API/WS (supertest)
npm run dev          # сервер на :8080 (tsx watch)
npm run dev:web      # vite dev-сервер на :5173 с прокси /api и /ws на :8080

Тесты Playwright (e2e приёмочных сценариев): npm run build -w @fcl/web, затем npx playwright install chromium && npm test:e2e -w @fcl/web.

CI: npm run lint && npm test && npm run build.