commit 6b69d4f64c30ff3b04322e71abaa6e13ca5a7e49 Author: Verum Date: Wed Mar 18 15:44:11 2026 +0700 123 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..09e7f29 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,138 @@ +# ============================================================================= +# Git +# ============================================================================= +.git +.gitea +.github +.gitlab +.gitlab-ci.yml +.gitattributes +.pre-commit-config.yaml + +# ============================================================================= +# Python virtual environments +# ============================================================================= +.venv +venv +env +ENV + +# ============================================================================= +# Python cache +# ============================================================================= +__pycache__/ +*.py[cod] +*.pyo +*.pyd +*.so + +# ============================================================================= +# Python tooling +# ============================================================================= +.mypy_cache/ +.pytest_cache/ +.ruff_cache/ +.pytype/ +.pyre/ +.pyright/ + +# ============================================================================= +# Testing / Coverage +# ============================================================================= +.coverage +.coverage.* +htmlcov/ +.tox/ +.nox/ +tests/ +test/ +coverage.xml + +# ============================================================================= +# Build artifacts +# ============================================================================= +build/ +dist/ +.eggs/ +*.egg-info/ +pip-wheel-metadata/ + +# ============================================================================= +# Logs +# ============================================================================= +*.log +logs/ +log/ + +# ============================================================================= +# Node / Frontend +# ============================================================================= +node_modules/ +.next/ +.nuxt/ +out/ +coverage/ +*.tsbuildinfo + +# ============================================================================= +# IDE / Editor +# ============================================================================= +.idea/ +.vscode/ +*.swp +*.swo +*~ +.DS_Store +Thumbs.db + +# ============================================================================= +# Environment files +# ============================================================================= +.env +.env.* +!.env.example +!.env.sample + +# ============================================================================= +# Databases +# ============================================================================= +*.db +*.sqlite +*.sqlite3 + +# ============================================================================= +# Secrets +# ============================================================================= +*.pem +*.key +*.crt +*.p12 +*.pfx +secrets/ + +# ============================================================================= +# Temporary +# ============================================================================= +tmp/ +temp/ +*.tmp +*.temp +.cache/ + +# ============================================================================= +# Jupyter +# ============================================================================= +.ipynb_checkpoints/ + +# ============================================================================= +# ML artifacts +# ============================================================================= +*.pt +*.pth +*.onnx +*.h5 +*.ckpt +*.safetensors +*.npy +*.npz +*.parquet diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d30f5f7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,65 @@ +root = true + +# ============================================================================= +# Global settings +# ============================================================================= +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 +tab_width = 4 + +# ============================================================================= +# Python +# ============================================================================= +[*.py] +max_line_length = 88 + +# ============================================================================= +# YAML (Docker, CI, compose) +# ============================================================================= +[*.yml] +indent_size = 2 + +[*.yaml] +indent_size = 2 + +# ============================================================================= +# JSON +# ============================================================================= +[*.json] +indent_size = 2 + +# ============================================================================= +# TOML (pyproject.toml, poetry) +# ============================================================================= +[*.toml] +indent_size = 2 + +# ============================================================================= +# Markdown +# ============================================================================= +[*.md] +trim_trailing_whitespace = false +indent_size = 2 + +# ============================================================================= +# Shell scripts +# ============================================================================= +[*.sh] +indent_size = 2 + +# ============================================================================= +# Makefile (tabs required) +# ============================================================================= +[Makefile] +indent_style = tab + +# ============================================================================= +# INI / config files +# ============================================================================= +[*.ini] +indent_size = 2 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7a089c0 --- /dev/null +++ b/.env.example @@ -0,0 +1,72 @@ +# ============================ +# ENVIRONMENT +# ============================ +NODE_ENV=production +DEBUG=false + +# ============================ +# SERVER +# ============================ +PORT=3000 +API_PORT=3000 +API_HOST=0.0.0.0 +FRONTEND_PORT=80 + +# ============================ +# DATABASE (PostgreSQL) +# ============================ +DB_HOST=postgres +DB_PORT=5432 +DB_NAME=pg_admin +DB_USER=postgres +DB_PASSWORD=CHANGE_ME_SECURE_PASSWORD +DB_HOST_DEV=localhost + +# Connection pool +MAX_CONNECTIONS=20 +CONNECTION_TIMEOUT=5000 +IDLE_TIMEOUT=30000 + +# ============================ +# AUTHENTICATION & SECURITY +# ============================ +JWT_SECRET=CHANGE_ME_SUPER_SECRET_JWT_KEY +JWT_EXPIRE=7d + +# ============================ +# REDIS CACHE +# ============================ +REDIS_HOST=redis +REDIS_PORT=6379 +REDIS_PASSWORD=CHANGE_ME_REDIS_PASSWORD +REDIS_DB=0 + +# ============================ +# FRONTEND +# ============================ +FRONTEND_API_URL=http://api.yourdomain.com/api +REACT_APP_ENV=production + +# ============================ +# NGINX +# ============================ +NGINX_PORT=8080 + +# ============================ +# CORS & RATE LIMITING +# ============================ +CORS_ORIGIN=https://yourdomain.com +RATE_LIMIT_WINDOW=15 +RATE_LIMIT_MAX_REQUESTS=100 + +# ============================ +# LOGGING +# ============================ +LOG_LEVEL=warn +LOG_FORMAT=json + +# ============================ +# MONITORING +# ============================ +SENTRY_DSN=https://your-sentry-dsn@sentry.io/project-id + diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5e5113f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,83 @@ +# ============================================================================= +# Global text normalization +# ============================================================================= +* text=auto eol=lf + +# ============================================================================= +# Shell scripts (must stay LF) +# ============================================================================= +*.sh text eol=lf +*.bash text eol=lf +*.zsh text eol=lf + +# ============================================================================= +# Windows scripts +# ============================================================================= +*.bat text eol=crlf +*.cmd text eol=crlf +*.ps1 text eol=crlf + +# ============================================================================= +# Binary images +# ============================================================================= +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.bmp binary +*.webp binary +*.ico binary + +# SVG is text +*.svg text + +# ============================================================================= +# Media +# ============================================================================= +*.mp3 binary +*.wav binary +*.ogg binary +*.mp4 binary +*.mov binary +*.avi binary +*.mkv binary + +# ============================================================================= +# Fonts +# ============================================================================= +*.eot binary +*.ttf binary +*.woff binary +*.woff2 binary +*.otf binary + +# ============================================================================= +# Documents +# ============================================================================= +*.pdf binary + +# ============================================================================= +# WebAssembly +# ============================================================================= +*.wasm binary + +# ============================================================================= +# Jupyter +# ============================================================================= +*.ipynb binary + +# ============================================================================= +# Git LFS (ML / large artifacts) +# ============================================================================= +*.pt filter=lfs diff=lfs merge=lfs -text +*.pth filter=lfs diff=lfs merge=lfs -text +*.onnx filter=lfs diff=lfs merge=lfs -text +*.ckpt filter=lfs diff=lfs merge=lfs -text +*.safetensors filter=lfs diff=lfs merge=lfs -text + +# ============================================================================= +# GitHub linguist hints +# ============================================================================= +docs/** linguist-documentation +generated/** linguist-generated +vendor/** linguist-vendored diff --git a/.gitea/ISSUE_TEMPLATE/bug_report.md b/.gitea/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..9ccef64 --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,24 @@ +--- +name: Bug report +about: Сообщить об ошибке +title: "[BUG]" +labels: bug +--- + +## Описание + +Опишите проблему. + +## Как воспроизвести + +1. ... +2. ... +3. ... + +## Ожидаемое поведение + +Что должно было произойти. + +## Логи / скриншоты + +Добавьте при необходимости. diff --git a/.gitea/ISSUE_TEMPLATE/feature_request.md b/.gitea/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..5ecd3a5 --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,16 @@ +--- +name: Feature request +about: Предложить новую функцию +title: "[FEATURE]" +labels: enhancement +--- + +## Описание функции + +Опишите идею. + +## Зачем это нужно + +Какая проблема решается. + +## Дополнительная информация diff --git a/.gitea/PULL_REQUEST_TEMPLATE.md b/.gitea/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..882e97b --- /dev/null +++ b/.gitea/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,14 @@ +## Описание + +Что делает этот PR? + +## Тип изменения + +- [ ] bug fix +- [ ] новая функция +- [ ] рефакторинг + +## Проверки + +- [ ] тесты проходят +- [ ] код отформатирован diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..41bfca2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,166 @@ +# ============================================================================= +# OS +# ============================================================================= +.DS_Store +Thumbs.db +Desktop.ini + +# ============================================================================= +# IDE / Editors +# ============================================================================= +.idea/ +.vscode/ +*.swp +*.swo +*~ +*.sublime-* +*.code-workspace + +# ============================================================================= +# Logs +# ============================================================================= +*.log +*.logs +*.logs.* +*.log.* +logs/ +log/ + +# ============================================================================= +# Environment / Secrets +# ============================================================================= +.env +.env.* +!.env.example +!.env.sample +!.env.template + +# ============================================================================= +# Security keys +# ============================================================================= +*.pem +*.key +*.crt +*.p12 +*.pfx +secrets/ + +# ============================================================================= +# Python +# ============================================================================= +__pycache__/ +*.py[cod] +*$py.class +*.so + +# Virtual environments +.venv/ +venv/ +env/ +ENV/ + +# Packaging +build/ +dist/ +.eggs/ +*.egg-info/ +pip-wheel-metadata/ + +# Testing / coverage +.coverage +.coverage.* +htmlcov/ +.tox/ +.nox/ + +# Tool caches +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +.pyre/ +.pytype/ +.pyright/ + +# Jupyter +.ipynb_checkpoints/ + +# ============================================================================= +# Node / Frontend +# ============================================================================= +node_modules/ +.next/ +.nuxt/ +coverage/ +*.tsbuildinfo + +# ============================================================================= +# Java / Kotlin +# ============================================================================= +.gradle/ +out/ +*.class + +# ============================================================================= +# Go +# ============================================================================= +bin/ +*.test + +# ============================================================================= +# Rust +# ============================================================================= +target/ + +# ============================================================================= +# C / C++ / CMake +# ============================================================================= +cmake-build-*/ +CMakeFiles/ +CMakeCache.txt +compile_commands.json + +# ============================================================================= +# Docker +# ============================================================================= +docker-compose.override.yml +*.tar + +# ============================================================================= +# Databases +# ============================================================================= +*.sqlite +*.sqlite3 +*.db + +# ============================================================================= +# ML / Data artifacts +# ============================================================================= +*.pt +*.pth +*.onnx +*.h5 +*.ckpt +*.safetensors +*.npy +*.npz +*.parquet +*.joblib +*.pkl +*.pickle + +# ============================================================================= +# Archives +# ============================================================================= +*.zip +*.tar.* +*.gz +*.7z +*.rar + +# ============================================================================= +# Temporary +# ============================================================================= +tmp/ +temp/ +*.tmp +.cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..7fc6da8 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,55 @@ +repos: + # ============================================================================= + # Ruff (lint + import sorting + formatting) + # ============================================================================= + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.4 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + - id: ruff-format + + # ============================================================================= + # Base repository hygiene + # ============================================================================= + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-yaml + args: [--allow-multiple-documents] + - id: check-json + - id: check-toml + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-merge-conflict + - id: detect-private-key + - id: check-added-large-files + - id: debug-statements + - id: check-executables-have-shebangs + - id: requirements-txt-fixer + + # ============================================================================= + # Static typing + # ============================================================================= + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.10.0 + hooks: + - id: mypy + args: [--ignore-missing-imports] + + # ============================================================================= + # Security checks + # ============================================================================= + - repo: https://github.com/PyCQA/bandit + rev: 1.7.8 + hooks: + - id: bandit + args: ["-r", "src"] + + # ============================================================================= + # Secret detection + # ============================================================================= + - repo: https://github.com/Yelp/detect-secrets + rev: v1.5.0 + hooks: + - id: detect-secrets diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cd6e479 --- /dev/null +++ b/Makefile @@ -0,0 +1,75 @@ +# Имя проекта (можно менять) +PROJECT_NAME=postgres_admin + +# Файл compose +COMPOSE=docker-compose + +# === Основные команды === + +# Сборка контейнеров +build: + $(COMPOSE) build + +# Запуск (в фоне) +up: + $(COMPOSE) up -d + +# Остановка +down: + $(COMPOSE) down + +# Перезапуск +restart: + $(COMPOSE) down + $(COMPOSE) up -d + +# Пересборка + запуск +rebuild: + $(COMPOSE) up -d --build + +# === Логи === + +# Все логи +logs: + $(COMPOSE) logs -f + +# Логи backend +logs-app: + $(COMPOSE) logs -f backend + +# Логи базы +logs-db: + $(COMPOSE) logs -f postgres + +# === Обслуживание === + +# Зайти в контейнер backend +bash: + $(COMPOSE) exec backend sh + +# Зайти в postgres +psql: + $(COMPOSE) exec postgres psql -U postgres -d testdb + +# Очистка (осторожно — удаляет данные!) +clean: + $(COMPOSE) down -v + docker system prune -f + +# Полный ресет (жёстко) +reset: + $(COMPOSE) down -v --remove-orphans + docker system prune -af + +# === Обновление === + +# Обновить код + пересобрать +update: + git pull + $(COMPOSE) up -d --build + +# === Статус === + +# Проверить контейнеры +ps: + $(COMPOSE) ps diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..13edced --- /dev/null +++ b/backend/package.json @@ -0,0 +1,49 @@ +{ + "name": "pg-admin-backend", + "version": "1.0.0", + "description": "PostgreSQL Admin Panel Backend API", + "main": "src/index.js", + "type": "module", + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + }, + "scripts": { + "start": "node src/index.js", + "dev": "nodemon src/index.js", + "test": "jest --coverage", + "test:watch": "jest --watch", + "lint": "eslint src --fix", + "format": "prettier --write 'src/**/*.js'" + }, + "keywords": [ + "postgresql", + "admin", + "database", + "management" + ], + "author": "", + "license": "MIT", + "dependencies": { + "express": "^4.18.2", + "express-async-errors": "^3.1.1", + "pg": "^8.9.0", + "dotenv": "^16.0.3", + "bcryptjs": "^2.4.3", + "jsonwebtoken": "^9.0.0", + "cors": "^2.8.5", + "helmet": "^7.0.0", + "express-rate-limit": "^6.7.0", + "joi": "^17.9.0", + "winston": "^3.8.2", + "axios": "^1.4.0", + "uuid": "^9.0.0" + }, + "devDependencies": { + "nodemon": "^3.0.1", + "eslint": "^8.54.0", + "prettier": "^3.1.0", + "jest": "^29.7.0", + "supertest": "^6.3.3" + } +} diff --git a/backend/src/index.js b/backend/src/index.js new file mode 100644 index 0000000..feeccbe --- /dev/null +++ b/backend/src/index.js @@ -0,0 +1,36 @@ +import express from 'express'; +import cors from 'cors'; +import helmet from 'helmet'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const app = express(); +const PORT = process.env.PORT || 3000; + +// Middleware +app.use(helmet()); +app.use(cors()); +app.use(express.json()); + +// Health check +app.get('/api/health', (req, res) => { + res.json({ status: 'OK', timestamp: new Date().toISOString() }); +}); + +// Basic info +app.get('/api', (req, res) => { + res.json({ + name: 'PostgreSQL Admin API', + version: '1.0.0', + status: 'running' + }); +}); + +// Start server +app.listen(PORT, () => { + console.log(`✅ Backend server running on port ${PORT}`); + console.log(`Environment: ${process.env.NODE_ENV || 'development'}`); +}); + +export default app; diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5980fa3 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,110 @@ +services: + postgres: + image: postgres:15-alpine + container_name: pg-admin-db + environment: + POSTGRES_USER: ${DB_USER:-postgres} + POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres} + POSTGRES_DB: ${DB_NAME:-pg_admin} + POSTGRES_INITDB_ARGS: "--encoding=UTF8" + ports: + - "${DB_PORT:-5432}:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./docker/init-db.sql:/docker-entrypoint-initdb.d/init.sql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-pg_admin}"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - pg-admin-network + + backend: + build: + context: ./backend + dockerfile: ../docker/Dockerfile.backend + container_name: pg-admin-api + environment: + NODE_ENV: ${NODE_ENV:-production} + PORT: ${API_PORT:-3000} + DB_HOST: postgres + DB_PORT: 5432 + DB_USER: ${DB_USER:-postgres} + DB_PASSWORD: ${DB_PASSWORD:-postgres} + DB_NAME: ${DB_NAME:-pg_admin} + JWT_SECRET: ${JWT_SECRET:-change_me_in_production} + JWT_EXPIRE: ${JWT_EXPIRE:-7d} + MAX_CONNECTIONS: ${MAX_CONNECTIONS:-20} + LOG_LEVEL: ${LOG_LEVEL:-info} + ports: + - "${API_PORT:-3000}:${API_PORT:-3000}" + depends_on: + postgres: + condition: service_healthy + volumes: + - ./backend/src:/app/src + - ./backend/logs:/app/logs + networks: + - pg-admin-network + restart: unless-stopped + + frontend: + build: + context: ./frontend + dockerfile: ../docker/Dockerfile.frontend + container_name: pg-admin-ui + environment: + REACT_APP_API_URL: ${FRONTEND_API_URL:-http://localhost:3000/api} + REACT_APP_ENV: ${NODE_ENV:-production} + ports: + - "${FRONTEND_PORT:-80}:3000" + depends_on: + - backend + volumes: + - ./frontend/src:/app/src + networks: + - pg-admin-network + restart: unless-stopped + + redis: + image: redis:7-alpine + container_name: pg-admin-cache + ports: + - "${REDIS_PORT:-6379}:6379" + environment: + REDIS_PASSWORD: ${REDIS_PASSWORD:-change_me} + volumes: + - redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - pg-admin-network + restart: unless-stopped + + nginx: + image: nginx:alpine + container_name: pg-admin-proxy + ports: + - "${NGINX_PORT:-8080}:80" + volumes: + - ./docker/nginx.conf:/etc/nginx/nginx.conf:ro + depends_on: + - frontend + - backend + networks: + - pg-admin-network + restart: unless-stopped + +volumes: + postgres_data: + driver: local + redis_data: + driver: local + +networks: + pg-admin-network: + driver: bridge diff --git a/docker/Dockerfile.backend b/docker/Dockerfile.backend new file mode 100644 index 0000000..fee811c --- /dev/null +++ b/docker/Dockerfile.backend @@ -0,0 +1,28 @@ +FROM node:18-alpine + +WORKDIR /app + +# Install curl for healthchecks +RUN apk add --no-cache curl + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm install 2>/dev/null || npm install --legacy-peer-deps + +# Copy source code +COPY src ./src + +# Create logs directory +RUN mkdir -p logs + +# Expose port +EXPOSE 3000 + +# Health check +HEALTHCHECK --interval=10s --timeout=5s --retries=5 \ + CMD curl -f http://localhost:3000/api/health || exit 1 + +# Start application +CMD ["node", "src/index.js"] diff --git a/docker/Dockerfile.frontend b/docker/Dockerfile.frontend new file mode 100644 index 0000000..42c3e00 --- /dev/null +++ b/docker/Dockerfile.frontend @@ -0,0 +1,28 @@ +# Build stage +FROM node:18-alpine AS builder + +WORKDIR /app + +COPY package*.json ./ + +RUN npm install + +COPY . . + +RUN npm run build 2>/dev/null || true + +# Production stage +FROM node:18-alpine + +WORKDIR /app + +RUN npm install -g serve + +COPY --from=builder /app/build ./build + +EXPOSE 3000 + +HEALTHCHECK --interval=10s --timeout=5s --retries=5 \ + CMD curl -f http://localhost:3000 || exit 1 + +CMD ["serve", "-s", "build", "-l", "3000"] diff --git a/docker/init-db.sql b/docker/init-db.sql new file mode 100644 index 0000000..0aff3ac --- /dev/null +++ b/docker/init-db.sql @@ -0,0 +1,92 @@ +-- ============================ +-- PostgreSQL Admin Default Setup +-- ============================ + +-- Create extensions +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE EXTENSION IF NOT EXISTS "pgcrypto"; + +-- Create users table +CREATE TABLE IF NOT EXISTS users ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + username VARCHAR(255) UNIQUE NOT NULL, + email VARCHAR(255) UNIQUE NOT NULL, + password_hash VARCHAR(255) NOT NULL, + role VARCHAR(50) NOT NULL DEFAULT 'user', + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + last_login TIMESTAMP +); + +-- Create indexes for users table +CREATE INDEX idx_users_username ON users(username); +CREATE INDEX idx_users_email ON users(email); +CREATE INDEX idx_users_role ON users(role); +CREATE INDEX idx_users_is_active ON users(is_active); + +-- Create audit log table +CREATE TABLE IF NOT EXISTS audit_logs ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID REFERENCES users(id) ON DELETE SET NULL, + action VARCHAR(255) NOT NULL, + resource_type VARCHAR(100), + resource_id VARCHAR(255), + old_values JSONB, + new_values JSONB, + ip_address INET, + user_agent TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Create indexes for audit logs +CREATE INDEX idx_audit_logs_user_id ON audit_logs(user_id); +CREATE INDEX idx_audit_logs_resource ON audit_logs(resource_type, resource_id); +CREATE INDEX idx_audit_logs_created_at ON audit_logs(created_at DESC); + +-- Create sessions table +CREATE TABLE IF NOT EXISTS sessions ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + token_hash VARCHAR(255) UNIQUE NOT NULL, + ip_address INET, + user_agent TEXT, + expires_at TIMESTAMP NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Create indexes for sessions +CREATE INDEX idx_sessions_user_id ON sessions(user_id); +CREATE INDEX idx_sessions_expires_at ON sessions(expires_at); + +-- Insert default admin user (password: admin123) +INSERT INTO users (username, email, password_hash, role) +VALUES ( + 'admin', + 'admin@pg-admin.local', + '$2b$10$YIjlrjVeuK7VxFVG8nXXXulXQI8m3Nw5VzVnlXoXVnlXoXVnlXoXV', + 'admin' +) ON CONFLICT (username) DO NOTHING; + +-- Create function to update updated_at timestamp +CREATE OR REPLACE FUNCTION update_updated_at_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = CURRENT_TIMESTAMP; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Create trigger for users table +CREATE TRIGGER update_users_updated_at + BEFORE UPDATE ON users + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + +-- Grant permissions to application user +GRANT CONNECT ON DATABASE pg_admin TO postgres; +GRANT USAGE ON SCHEMA public TO postgres; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO postgres; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO postgres; + +COMMIT; diff --git a/docker/nginx.conf b/docker/nginx.conf new file mode 100644 index 0000000..b0cc43d --- /dev/null +++ b/docker/nginx.conf @@ -0,0 +1,104 @@ +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; + use epoll; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + client_max_body_size 20M; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml text/javascript + application/json application/javascript application/xml+rss + application/rss+xml font/truetype font/opentype + application/vnd.ms-fontobject image/svg+xml; + + # Upstream servers + upstream frontend { + server frontend:3000; + } + + upstream backend { + server backend:3000; + } + + # Rate limiting + limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s; + limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s; + + # HTTP to HTTPS redirect (uncomment for production with SSL) + # server { + # listen 80; + # return 301 https://$host$request_uri; + # } + + server { + listen 80; + server_name _; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + + # Frontend + location / { + proxy_pass http://frontend; + 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; + } + + # API + location /api/ { + limit_req zone=api burst=60 nodelay; + + proxy_pass http://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_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..5c3c420 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,42 @@ +{ + "name": "pg-admin-frontend", + "version": "1.0.0", + "description": "PostgreSQL Admin Panel Frontend", + "private": true, + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.18.0", + "axios": "^1.6.2" + }, + "devDependencies": { + "react-scripts": "5.0.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/frontend/public/index.html b/frontend/public/index.html new file mode 100644 index 0000000..1077699 --- /dev/null +++ b/frontend/public/index.html @@ -0,0 +1,17 @@ + + + + + + + + PostgreSQL Admin + + + +
+ + diff --git a/frontend/src/App.js b/frontend/src/App.js new file mode 100644 index 0000000..9fb130b --- /dev/null +++ b/frontend/src/App.js @@ -0,0 +1,13 @@ +import React from 'react'; + +function App() { + return ( +
+

PostgreSQL Admin Panel

+

Frontend is loading...

+

API Status: Checking...

+
+ ); +} + +export default App; diff --git a/frontend/src/index.js b/frontend/src/index.js new file mode 100644 index 0000000..593edf1 --- /dev/null +++ b/frontend/src/index.js @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( + + + +);