base commit
This commit is contained in:
@@ -50,17 +50,31 @@
|
|||||||
|
|
||||||
### Бэкенд
|
### Бэкенд
|
||||||
|
|
||||||
|
**Вариант A — типовой `.env`**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd server
|
cd server
|
||||||
cp .env.example .env # укажите ADMIN_EMAIL
|
cp .env.example .env # укажите ADMIN_EMAIL
|
||||||
npm install
|
npm install
|
||||||
npx prisma migrate dev # если база ещё не создана
|
npx prisma migrate dev # если база ещё не создана
|
||||||
npx prisma db seed # опционально: тестовые категории и товары
|
npx prisma db seed # опционально: тестовые категории и товары
|
||||||
npm run dev
|
npm run dev:classic # загрузка из `.env`
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Вариант B — файл [`server/.dev_env`](server/.dev_env)** (то, что уже лежит в репозитории для локального стенда; нужен **Node.js 20.6+** из‑за `node --env-file`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
npm install
|
||||||
|
npm run dev # переменные из `.dev_env`
|
||||||
|
```
|
||||||
|
|
||||||
|
Очистка БД до «чистого» тестового состояния (SQLite + миграции + seed): в `server/` выполните `npm run db:reset:test`.
|
||||||
|
|
||||||
Сервер: `http://127.0.0.1:3333`. Проверка: `GET /health`.
|
Сервер: `http://127.0.0.1:3333`. Проверка: `GET /health`.
|
||||||
|
|
||||||
|
Черновик деплоя на Proxmox: [docs/test-deploy-proxmox.md](docs/test-deploy-proxmox.md).
|
||||||
|
|
||||||
### Фронтенд
|
### Фронтенд
|
||||||
|
|
||||||
В другом терминале:
|
В другом терминале:
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# Тестовый деплой на Proxmox (локально у вас в ЦОД)
|
||||||
|
|
||||||
|
Цель — один недорогой стенд: витрина + API + SQLite, без отказоустойчивого кластера.
|
||||||
|
|
||||||
|
## Серверов и ресурсов
|
||||||
|
|
||||||
|
Рекомендация для **минимального тестового** инстанса на Proxmox:
|
||||||
|
|
||||||
|
| Вариант | Ресурс | Комментарий |
|
||||||
|
|---------|--------|---------------|
|
||||||
|
| **1× LXC Debian 12 или Ubuntu 22.04** | CPU: **2 vCPU**, RAM: **1–2 GB**, Диск: **15–25 GB** (SSD/pool SSD) | Один контейнер: Node (API), при желании статика фронта с того же узла через nginx |
|
||||||
|
|
||||||
|
Для сборки (`npm ci` + `vite build`) и запасов по памяти лучше **2 GB RAM**; на **1 GB** возможны OOM при параллельной сборке — собирать фронт можно на машине разработчика и копировать `client/dist`.
|
||||||
|
|
||||||
|
Отделять приложение на **два LXC** (фронт / бэкенд) для теста обычно не нужно; имеет смысл только если хотите явно ограничить поверхность API.
|
||||||
|
|
||||||
|
### Сеть и доступ
|
||||||
|
|
||||||
|
- Одна виртуальная сеть VM/LAN вашего хостинга до Proxmox.
|
||||||
|
- Открыть снаружи (если нужен доступ из браузера): **80/tcp** и **443/tcp** на reverse proxy; прямой порт Fastify (**3333**) наружу не публиковать.
|
||||||
|
- Сервер приложения слушает **опционально** `127.0.0.1:3333`, наружу отдаёт **nginx** или **caddy**:
|
||||||
|
|
||||||
|
- статика из `client/dist` (SPA `try_files`);
|
||||||
|
|
||||||
|
- `location /api/` → proxy на `http://127.0.0.1:3333`;
|
||||||
|
|
||||||
|
- `location /uploads/` → те же файлы с диска, что использует сервер (`server/uploads/` рядом с процессом) или через тот же origin с proxy на Fastify `@fastify/static`.
|
||||||
|
|
||||||
|
## Очистка БД локально перед тестом
|
||||||
|
|
||||||
|
В каталоге `server/` (нужны Node **20.6+** или **22+** для флага `--env-file`; иначе скопируйте переменные из `.dev_env` в `.env` и используйте обычные команды Prisma):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run db:reset:test
|
||||||
|
```
|
||||||
|
|
||||||
|
Удалит данные SQLite, заново применит миграции и выполнит `prisma`-seed из `server/package.json`.
|
||||||
|
|
||||||
|
## Программный стек на ВМ/LXC
|
||||||
|
|
||||||
|
- **Node.js** LTS (20.6+, лучше 22; скрипты `dev`, `start:dev_env`, `db:*:test` читают `server/.dev_env` через `node --env-file=.dev_env`).
|
||||||
|
- Приложение ставится так: склонировать репозиторий, на сервере в `server/` — `npm ci`, `npm run db:migrate:test` (или `migrate deploy`), при необходимости `npm run db:seed:test`.
|
||||||
|
- Переменные окружения: скопировать `server/.dev_env` или `server/.env.example`, задать **сильный** `JWT_SECRET`, свой `ADMIN_EMAIL`, `DATABASE_URL=file:./dev.db` или путь под персистентный раздел (`/var/lib/craftshop/data.db`).
|
||||||
|
- Для прод-подобного теста: **`IS_DEFAULT_CODE_ENABLED=false`** (не оставлять общий код на поставку наружу).
|
||||||
|
|
||||||
|
## Сервис (systemd, пример)
|
||||||
|
|
||||||
|
Один юнит `craftshop-api.service`:
|
||||||
|
|
||||||
|
- `WorkingDirectory=/opt/craftshop/server`
|
||||||
|
- `ExecStart=/usr/bin/node src/index.js` (перед этим экспортировать env через `EnvironmentFile=` к вашему `.env`)
|
||||||
|
- После деплоя: `systemctl daemon-reload && systemctl enable --now craftshop-api`
|
||||||
|
|
||||||
|
Персистентность: файл SQLite и каталог **`uploads/`** должны жить на диске, который не теряется при пересборке образа контейнера.
|
||||||
|
|
||||||
|
## Контрольный чеклист перед «тестом в бою»
|
||||||
|
|
||||||
|
1. Прогон миграций на чистую БД: `npm run db:reset:test` (только в **закрытом** окружении — стирает данные).
|
||||||
|
2. Сборка фронта и выставление `VITE_API_URL` (если API не на том же origin, что SPA).
|
||||||
|
3. `CORS_ORIGIN` — URL публичного фронта.
|
||||||
|
4. Выключить `DEFAULT_CODE` на внешнем стенде.
|
||||||
|
|
||||||
|
## Краткая схема
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
User[Браузер]
|
||||||
|
Px[nginx на LXC]
|
||||||
|
Api[Fastify Node :3333]
|
||||||
|
Db[(SQLite файл)]
|
||||||
|
Up[диск uploads]
|
||||||
|
|
||||||
|
User --> Px
|
||||||
|
Px -->|"/api"| Api
|
||||||
|
Px -->|"статика /"| Ui[client dist]
|
||||||
|
Api --> Db
|
||||||
|
Api --> Up
|
||||||
|
```
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
DATABASE_URL="file:./dev.db"
|
||||||
|
ADMIN_EMAIL=admin@example.com
|
||||||
|
# Default code for login
|
||||||
|
DEFAULT_CODE=123456
|
||||||
|
IS_DEFAULT_CODE_ENABLED=true
|
||||||
|
PORT=3333
|
||||||
|
# Токен для админ-запросов с фронта: Authorization: Bearer <значение>
|
||||||
|
ADMIN_API_TOKEN=dev-secret-change-me
|
||||||
|
# JWT для пользовательской авторизации (замени в проде)
|
||||||
|
JWT_SECRET=dev-jwt-secret-change-me
|
||||||
|
# Опционально: список origin для CORS через запятую (в dev можно не задавать)
|
||||||
|
# CORS_ORIGIN=http://localhost:5173
|
||||||
|
|
||||||
|
# SMTP для отправки кода (если не задано — код логируется в консоль как [DEV])
|
||||||
|
# SMTP_HOST=smtp.example.com
|
||||||
|
# SMTP_PORT=587
|
||||||
|
# SMTP_SECURE=false
|
||||||
|
# SMTP_USER=user@example.com
|
||||||
|
# SMTP_PASS=password
|
||||||
|
# MAIL_FROM="Craftshop <no-reply@example.com>"
|
||||||
@@ -3,6 +3,10 @@ PORT=3333
|
|||||||
ADMIN_EMAIL=admin@example.com
|
ADMIN_EMAIL=admin@example.com
|
||||||
JWT_SECRET=замените-на-секрет-jwt
|
JWT_SECRET=замените-на-секрет-jwt
|
||||||
|
|
||||||
|
# Только приватный стенд: фиксированный код входа (без письма), см. server/.dev_env и npm run dev/start:dev_env
|
||||||
|
# IS_DEFAULT_CODE_ENABLED=true
|
||||||
|
# DEFAULT_CODE=123456
|
||||||
|
|
||||||
# Разрешённый Origin фронта (через запятую при нескольких)
|
# Разрешённый Origin фронта (через запятую при нескольких)
|
||||||
# CORS_ORIGIN=http://127.0.0.1:5173
|
# CORS_ORIGIN=http://127.0.0.1:5173
|
||||||
|
|
||||||
|
|||||||
+8
-2
@@ -4,11 +4,17 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node --watch src/index.js",
|
"dev": "node --env-file=.dev_env --watch src/index.js",
|
||||||
|
"dev:classic": "node --watch src/index.js",
|
||||||
"start": "node src/index.js",
|
"start": "node src/index.js",
|
||||||
|
"start:dev_env": "node --env-file=.dev_env src/index.js",
|
||||||
"db:migrate": "prisma migrate dev",
|
"db:migrate": "prisma migrate dev",
|
||||||
|
"db:migrate:test": "node --env-file=.dev_env ./node_modules/prisma/build/index.js migrate deploy",
|
||||||
"db:seed": "prisma db seed",
|
"db:seed": "prisma db seed",
|
||||||
"db:studio": "prisma studio"
|
"db:seed:test": "node --env-file=.dev_env prisma/seed.js",
|
||||||
|
"db:reset:test": "node --env-file=.dev_env ./node_modules/prisma/build/index.js migrate reset --force",
|
||||||
|
"db:studio": "prisma studio",
|
||||||
|
"db:studio:test": "node --env-file=.dev_env ./node_modules/prisma/build/index.js studio"
|
||||||
},
|
},
|
||||||
"prisma": {
|
"prisma": {
|
||||||
"seed": "node prisma/seed.js"
|
"seed": "node prisma/seed.js"
|
||||||
|
|||||||
@@ -29,7 +29,22 @@ export async function issueEmailCode({ email, purpose, userId = null }) {
|
|||||||
await sendLoginCodeEmail({ to: email, code })
|
await sendLoginCodeEmail({ to: email, code })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseEnvBool(raw) {
|
||||||
|
const v = String(raw ?? '').trim().toLowerCase()
|
||||||
|
return v === 'true' || v === '1' || v === 'yes'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Тестовые стенды: принять код из переменной DEFAULT_CODE без записи в БД. */
|
||||||
|
export function isDefaultLoginCodeAccepted(codeInput) {
|
||||||
|
if (!parseEnvBool(process.env.IS_DEFAULT_CODE_ENABLED)) return false
|
||||||
|
const expected = String(process.env.DEFAULT_CODE ?? '').trim()
|
||||||
|
if (!expected || expected.length < 4) return false
|
||||||
|
return String(codeInput ?? '').trim() === expected
|
||||||
|
}
|
||||||
|
|
||||||
export async function verifyEmailCode({ email, purpose, code, userId = null }) {
|
export async function verifyEmailCode({ email, purpose, code, userId = null }) {
|
||||||
|
if (purpose === 'login' && isDefaultLoginCodeAccepted(code)) return true
|
||||||
|
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const codeHash = sha256(`${email}:${purpose}:${code}:${userId ?? ''}`)
|
const codeHash = sha256(`${email}:${purpose}:${code}:${userId ?? ''}`)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user