Fix initial user creating
This commit is contained in:
12
.env.example
12
.env.example
@@ -5,10 +5,18 @@ DB_PASSWORD=your_secure_password_here
|
|||||||
API_KEY_SALT=generate_random_salt_with_openssl
|
API_KEY_SALT=generate_random_salt_with_openssl
|
||||||
CORS_ORIGIN=http://localhost:3001
|
CORS_ORIGIN=http://localhost:3001
|
||||||
|
|
||||||
|
# Bootstrap admin
|
||||||
|
BOOTSTRAP_ADMIN=true
|
||||||
|
BOOTSTRAP_ADMIN_RESET_PASSWORD=false
|
||||||
|
ADMIN_USERNAME=admin
|
||||||
|
ADMIN_PASSWORD=change_this_admin_password
|
||||||
|
ADMIN_FULL_NAME=Main administrator
|
||||||
|
ADMIN_EMAIL=admin@polotsk-transit.local
|
||||||
|
|
||||||
# Frontend
|
# Frontend
|
||||||
API_URL=http://localhost:3000/api/v1
|
API_URL=/api/v1
|
||||||
|
|
||||||
# Production settings (uncomment for production)
|
# Production settings (uncomment for production)
|
||||||
# NODE_ENV=production
|
# NODE_ENV=production
|
||||||
# CORS_ORIGIN=https://admin.yourdomain.com
|
# CORS_ORIGIN=https://admin.yourdomain.com
|
||||||
# API_URL=https://api.yourdomain.com/api/v1
|
# API_URL=/api/v1
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ cp .env.example .env
|
|||||||
# Сгенерируйте безопасный пароль
|
# Сгенерируйте безопасный пароль
|
||||||
openssl rand -base64 32
|
openssl rand -base64 32
|
||||||
|
|
||||||
# Отредактируйте .env и вставьте сгенерированный пароль в DB_PASSWORD
|
# Отредактируйте .env и вставьте сгенерированные пароли в DB_PASSWORD и ADMIN_PASSWORD
|
||||||
nano .env
|
nano .env
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -56,6 +56,8 @@ curl -H "X-API-Key: dev_key_12345678" http://localhost:3000/api/v1/sync/status
|
|||||||
### Шаг 5: Откройте админ-панель
|
### Шаг 5: Откройте админ-панель
|
||||||
Откройте в браузере: http://localhost:3001
|
Откройте в браузере: http://localhost:3001
|
||||||
|
|
||||||
|
Войдите под `admin` и паролем из `ADMIN_PASSWORD` в `.env`.
|
||||||
|
|
||||||
**Готово! 🎉**
|
**Готово! 🎉**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -52,10 +52,11 @@ nano .env
|
|||||||
# Обязательно измените в продакшене!
|
# Обязательно измените в продакшене!
|
||||||
DB_PASSWORD=your_secure_password_here
|
DB_PASSWORD=your_secure_password_here
|
||||||
API_KEY_SALT=generate_random_salt_with_openssl
|
API_KEY_SALT=generate_random_salt_with_openssl
|
||||||
|
ADMIN_PASSWORD=your_secure_admin_password_here
|
||||||
|
|
||||||
# Для разработки оставьте как есть
|
# Для разработки/деплоя через встроенный proxy оставьте как есть
|
||||||
CORS_ORIGIN=http://localhost:3001
|
CORS_ORIGIN=http://localhost:3001
|
||||||
API_URL=http://localhost:3000/api/v1
|
API_URL=/api/v1
|
||||||
```
|
```
|
||||||
|
|
||||||
Генерация безопасного salt:
|
Генерация безопасного salt:
|
||||||
@@ -82,6 +83,10 @@ docker-compose logs -f
|
|||||||
- **PostgreSQL**: localhost:5432
|
- **PostgreSQL**: localhost:5432
|
||||||
- **Redis**: localhost:6379
|
- **Redis**: localhost:6379
|
||||||
|
|
||||||
|
Мастер-аккаунт создается автоматически при старте backend:
|
||||||
|
- login: `admin`
|
||||||
|
- password: значение `ADMIN_PASSWORD` из `.env` (если не задано, fallback `admin123`)
|
||||||
|
|
||||||
### 3. Проверка работоспособности
|
### 3. Проверка работоспособности
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"start": "node src/index.js",
|
"start": "node src/index.js",
|
||||||
"dev": "nodemon src/index.js",
|
"dev": "nodemon src/index.js",
|
||||||
"migrate": "node src/utils/migrate.js",
|
"migrate": "node src/utils/migrate.js",
|
||||||
|
"create-admin": "node scripts/create-admin.js",
|
||||||
"seed": "node src/utils/seed.js"
|
"seed": "node src/utils/seed.js"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,26 +1,22 @@
|
|||||||
const bcrypt = require('bcrypt');
|
|
||||||
const pool = require('../src/config/database');
|
const pool = require('../src/config/database');
|
||||||
|
const { ensureBootstrapAdmin, readBootstrapAdminConfig } = require('../src/utils/bootstrapAdmin');
|
||||||
|
|
||||||
async function createAdmin() {
|
async function createAdmin() {
|
||||||
const username = 'admin';
|
const config = readBootstrapAdminConfig();
|
||||||
const password = 'admin123'; // Измените после первого входа!
|
|
||||||
const passwordHash = await bcrypt.hash(password, 10);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await pool.query(`
|
const result = await ensureBootstrapAdmin({
|
||||||
INSERT INTO users (username, password_hash, full_name, email, role, is_active)
|
resetPassword: true,
|
||||||
VALUES ($1, $2, $3, $4, $5, $6)
|
createIfActiveAdminExists: true,
|
||||||
ON CONFLICT (username) DO UPDATE
|
});
|
||||||
SET password_hash = $2
|
|
||||||
RETURNING id, username, role
|
|
||||||
`, [username, passwordHash, 'Главный администратор', 'admin@polotsk-transit.local', 'admin', true]);
|
|
||||||
|
|
||||||
console.log('✅ Мастер-аккаунт создан:', result.rows[0]);
|
console.log('Master account is ready:', result.user);
|
||||||
console.log('Username: admin');
|
console.log(`Username: ${config.username}`);
|
||||||
console.log('Password: admin123');
|
console.log(`Password: ${config.password}`);
|
||||||
console.log('⚠️ ОБЯЗАТЕЛЬНО смените пароль после первого входа!');
|
console.log('Change this password after the first login.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Ошибка:', error);
|
console.error('Error:', error);
|
||||||
|
process.exitCode = 1;
|
||||||
} finally {
|
} finally {
|
||||||
await pool.end();
|
await pool.end();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ const compression = require('compression');
|
|||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
|
|
||||||
const logger = require('./config/logger');
|
const logger = require('./config/logger');
|
||||||
|
const pool = require('./config/database');
|
||||||
const apiRoutes = require('./routes/api');
|
const apiRoutes = require('./routes/api');
|
||||||
const { authenticateApiKey } = require('./middleware/auth');
|
const { authenticateApiKey } = require('./middleware/auth');
|
||||||
const createRateLimiter = require('./middleware/rateLimiter');
|
const createRateLimiter = require('./middleware/rateLimiter');
|
||||||
const { ensureRealtimeSchema } = require('./services/realtimeSchema');
|
const { ensureRealtimeSchema } = require('./services/realtimeSchema');
|
||||||
const { attachRealtimeServer, closeRealtimeServer } = require('./services/realtimeService');
|
const { attachRealtimeServer, closeRealtimeServer } = require('./services/realtimeService');
|
||||||
|
const { ensureBootstrapAdmin } = require('./utils/bootstrapAdmin');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
@@ -133,9 +135,31 @@ function shutdown(signal) {
|
|||||||
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
||||||
process.on('SIGINT', () => shutdown('SIGINT'));
|
process.on('SIGINT', () => shutdown('SIGINT'));
|
||||||
|
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForDatabase({ attempts = 30, delayMs = 1000 } = {}) {
|
||||||
|
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
||||||
|
try {
|
||||||
|
await pool.query('SELECT 1');
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
if (attempt === attempts) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.warn(`Database is not ready yet (${attempt}/${attempts}); retrying...`);
|
||||||
|
await sleep(delayMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function startServer() {
|
async function startServer() {
|
||||||
try {
|
try {
|
||||||
|
await waitForDatabase();
|
||||||
await ensureRealtimeSchema();
|
await ensureRealtimeSchema();
|
||||||
|
await ensureBootstrapAdmin();
|
||||||
|
|
||||||
server = app.listen(PORT, () => {
|
server = app.listen(PORT, () => {
|
||||||
logger.info(`Server is running on port ${PORT}`);
|
logger.info(`Server is running on port ${PORT}`);
|
||||||
|
|||||||
122
backend/src/utils/bootstrapAdmin.js
vendored
Normal file
122
backend/src/utils/bootstrapAdmin.js
vendored
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
const bcrypt = require('bcrypt');
|
||||||
|
|
||||||
|
const pool = require('../config/database');
|
||||||
|
const logger = require('../config/logger');
|
||||||
|
|
||||||
|
const DEFAULT_ADMIN = {
|
||||||
|
username: 'admin',
|
||||||
|
password: 'admin123',
|
||||||
|
fullName: 'Main administrator',
|
||||||
|
email: 'admin@polotsk-transit.local',
|
||||||
|
};
|
||||||
|
|
||||||
|
function readBoolean(value, defaultValue) {
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['1', 'true', 'yes', 'on'].includes(String(value).toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
function readBootstrapAdminConfig(env = process.env) {
|
||||||
|
return {
|
||||||
|
enabled: readBoolean(env.BOOTSTRAP_ADMIN, true),
|
||||||
|
username: env.ADMIN_USERNAME || DEFAULT_ADMIN.username,
|
||||||
|
password: env.ADMIN_PASSWORD || DEFAULT_ADMIN.password,
|
||||||
|
fullName: env.ADMIN_FULL_NAME || DEFAULT_ADMIN.fullName,
|
||||||
|
email: env.ADMIN_EMAIL || DEFAULT_ADMIN.email,
|
||||||
|
resetPassword: readBoolean(env.BOOTSTRAP_ADMIN_RESET_PASSWORD, false),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function activeAdminExists() {
|
||||||
|
const result = await pool.query(`
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM users
|
||||||
|
WHERE role = 'admin' AND is_active = true
|
||||||
|
) AS exists
|
||||||
|
`);
|
||||||
|
|
||||||
|
return result.rows[0]?.exists === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateConfig(config) {
|
||||||
|
if (!config.username) {
|
||||||
|
throw new Error('ADMIN_USERNAME cannot be empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.password || config.password.length < 6) {
|
||||||
|
throw new Error('ADMIN_PASSWORD must be at least 6 characters');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureBootstrapAdmin(options = {}) {
|
||||||
|
const config = {
|
||||||
|
...readBootstrapAdminConfig(),
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!config.enabled) {
|
||||||
|
logger.info('Bootstrap admin is disabled');
|
||||||
|
return { changed: false, reason: 'disabled' };
|
||||||
|
}
|
||||||
|
|
||||||
|
validateConfig(config);
|
||||||
|
|
||||||
|
const existingResult = await pool.query(
|
||||||
|
'SELECT id, username, role, is_active FROM users WHERE username = $1',
|
||||||
|
[config.username]
|
||||||
|
);
|
||||||
|
const existingUser = existingResult.rows[0];
|
||||||
|
|
||||||
|
if (existingUser) {
|
||||||
|
const needsRepair = existingUser.role !== 'admin' || existingUser.is_active !== true;
|
||||||
|
const shouldResetPassword = config.resetPassword || needsRepair;
|
||||||
|
|
||||||
|
if (!needsRepair && !shouldResetPassword) {
|
||||||
|
logger.info(`Bootstrap admin already exists: ${config.username}`);
|
||||||
|
return { changed: false, user: existingUser };
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordHash = shouldResetPassword
|
||||||
|
? await bcrypt.hash(config.password, 10)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const updateResult = await pool.query(`
|
||||||
|
UPDATE users
|
||||||
|
SET role = 'admin',
|
||||||
|
is_active = true,
|
||||||
|
full_name = COALESCE($2, full_name),
|
||||||
|
email = COALESCE($3, email),
|
||||||
|
password_hash = COALESCE($4, password_hash)
|
||||||
|
WHERE username = $1
|
||||||
|
RETURNING id, username, role, is_active
|
||||||
|
`, [config.username, config.fullName || null, config.email || null, passwordHash]);
|
||||||
|
|
||||||
|
logger.info(`Bootstrap admin repaired: ${config.username}`);
|
||||||
|
return { changed: true, user: updateResult.rows[0] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldCreateWithExistingAdmin = options.createIfActiveAdminExists === true;
|
||||||
|
if (!shouldCreateWithExistingAdmin && await activeAdminExists()) {
|
||||||
|
logger.info('Active admin already exists; default bootstrap admin was not created');
|
||||||
|
return { changed: false, reason: 'active-admin-exists' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordHash = await bcrypt.hash(config.password, 10);
|
||||||
|
const insertResult = await pool.query(`
|
||||||
|
INSERT INTO users (username, password_hash, full_name, email, role, is_active)
|
||||||
|
VALUES ($1, $2, $3, $4, 'admin', true)
|
||||||
|
RETURNING id, username, role, is_active
|
||||||
|
`, [config.username, passwordHash, config.fullName || null, config.email || null]);
|
||||||
|
|
||||||
|
logger.info(`Bootstrap admin created: ${config.username}`);
|
||||||
|
return { changed: true, user: insertResult.rows[0] };
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
DEFAULT_ADMIN,
|
||||||
|
ensureBootstrapAdmin,
|
||||||
|
readBootstrapAdminConfig,
|
||||||
|
};
|
||||||
BIN
backups/polotsk_transit.dump
Normal file
BIN
backups/polotsk_transit.dump
Normal file
Binary file not shown.
@@ -1,5 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgis/postgis:15-3.3
|
image: postgis/postgis:15-3.3
|
||||||
@@ -13,6 +11,11 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
- ./backend/migrations:/docker-entrypoint-initdb.d
|
- ./backend/migrations:/docker-entrypoint-initdb.d
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U transit_admin -d polotsk_transit"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 20
|
||||||
networks:
|
networks:
|
||||||
- transit-network
|
- transit-network
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -24,6 +27,11 @@ services:
|
|||||||
- "6379:6379"
|
- "6379:6379"
|
||||||
volumes:
|
volumes:
|
||||||
- redis_data:/data
|
- redis_data:/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 20
|
||||||
networks:
|
networks:
|
||||||
- transit-network
|
- transit-network
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -41,11 +49,19 @@ services:
|
|||||||
API_KEY_SALT: ${API_KEY_SALT:-generate_random_salt}
|
API_KEY_SALT: ${API_KEY_SALT:-generate_random_salt}
|
||||||
JWT_SECRET: ${JWT_SECRET:-change_me_in_production_use_random_string}
|
JWT_SECRET: ${JWT_SECRET:-change_me_in_production_use_random_string}
|
||||||
CORS_ORIGIN: ${CORS_ORIGIN:-http://localhost:3001}
|
CORS_ORIGIN: ${CORS_ORIGIN:-http://localhost:3001}
|
||||||
|
BOOTSTRAP_ADMIN: ${BOOTSTRAP_ADMIN:-true}
|
||||||
|
BOOTSTRAP_ADMIN_RESET_PASSWORD: ${BOOTSTRAP_ADMIN_RESET_PASSWORD:-false}
|
||||||
|
ADMIN_USERNAME: ${ADMIN_USERNAME:-admin}
|
||||||
|
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-admin123}
|
||||||
|
ADMIN_FULL_NAME: ${ADMIN_FULL_NAME:-Main administrator}
|
||||||
|
ADMIN_EMAIL: ${ADMIN_EMAIL:-admin@polotsk-transit.local}
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
postgres:
|
||||||
- redis
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
networks:
|
networks:
|
||||||
- transit-network
|
- transit-network
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -58,10 +74,10 @@ services:
|
|||||||
context: ./frontend
|
context: ./frontend
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
args:
|
args:
|
||||||
VITE_API_URL: ${API_URL:-http://localhost:3000/api/v1}
|
VITE_API_URL: ${API_URL:-/api/v1}
|
||||||
container_name: polotsk-transit-admin
|
container_name: polotsk-transit-admin
|
||||||
environment:
|
environment:
|
||||||
VITE_API_URL: ${API_URL:-http://localhost:3000/api/v1}
|
VITE_API_URL: ${API_URL:-/api/v1}
|
||||||
ports:
|
ports:
|
||||||
- "3001:80"
|
- "3001:80"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
VITE_API_URL=http://localhost:3000/api/v1
|
VITE_API_URL=/api/v1
|
||||||
VITE_API_KEY=dev_key_12345678
|
VITE_API_KEY=dev_key_12345678
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ FROM node:18-alpine as build
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ARG VITE_API_URL=http://localhost:3000/api/v1
|
ARG VITE_API_URL=/api/v1
|
||||||
ENV VITE_API_URL=$VITE_API_URL
|
ENV VITE_API_URL=$VITE_API_URL
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|||||||
@@ -10,6 +10,28 @@ server {
|
|||||||
gzip_min_length 1000;
|
gzip_min_length 1000;
|
||||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||||
|
|
||||||
|
# Same-origin API proxy for deployed admin panel
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://backend:3000;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /ws/ {
|
||||||
|
proxy_pass http://backend:3000;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "Upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_read_timeout 3600s;
|
||||||
|
}
|
||||||
|
|
||||||
# SPA routing
|
# SPA routing
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ export default function LoginPage() {
|
|||||||
Мастер-аккаунт по умолчанию:
|
Мастер-аккаунт по умолчанию:
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-gray-500 mt-1">
|
<p className="text-xs text-gray-500 mt-1">
|
||||||
<code className="bg-gray-100 px-2 py-1 rounded">admin / admin123</code>
|
<code className="bg-gray-100 px-2 py-1 rounded">admin / ADMIN_PASSWORD</code>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const DEFAULT_API_BASE_URL = 'http://localhost:3000/api/v1';
|
const DEFAULT_API_BASE_URL = '/api/v1';
|
||||||
|
|
||||||
function normalizePath(pathname) {
|
function normalizePath(pathname) {
|
||||||
const trimmedPath = (pathname || '').replace(/\/+$/, '');
|
const trimmedPath = (pathname || '').replace(/\/+$/, '');
|
||||||
@@ -40,6 +40,6 @@ export function getApiOrigin(rawValue = import.meta.env.VITE_API_URL || DEFAULT_
|
|||||||
const url = new URL(getApiBaseUrl(rawValue));
|
const url = new URL(getApiBaseUrl(rawValue));
|
||||||
return `${url.protocol}//${url.host}`;
|
return `${url.protocol}//${url.host}`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return 'http://localhost:3000';
|
return typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,17 @@ export default defineConfig({
|
|||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
server: {
|
server: {
|
||||||
host: true,
|
host: true,
|
||||||
port: 3001
|
port: 3001,
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:3000',
|
||||||
|
changeOrigin: true
|
||||||
|
},
|
||||||
|
'/ws': {
|
||||||
|
target: 'ws://localhost:3000',
|
||||||
|
ws: true
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir: 'dist',
|
outDir: 'dist',
|
||||||
|
|||||||
@@ -104,6 +104,32 @@ http {
|
|||||||
access_log /var/log/nginx/admin_access.log;
|
access_log /var/log/nginx/admin_access.log;
|
||||||
error_log /var/log/nginx/admin_error.log;
|
error_log /var/log/nginx/admin_error.log;
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
limit_req zone=api_limit burst=20 nodelay;
|
||||||
|
|
||||||
|
proxy_pass http://api_backend;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /ws/ {
|
||||||
|
proxy_pass http://api_backend;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "Upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_read_timeout 3600s;
|
||||||
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
limit_req zone=admin_limit burst=10 nodelay;
|
limit_req zone=admin_limit burst=10 nodelay;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user