Neatly packaged for docker
This commit is contained in:
parent
1cae3b3479
commit
21c8153519
69
.env.example
Normal file
69
.env.example
Normal file
@ -0,0 +1,69 @@
|
||||
# STUPA PDF API Configuration Example
|
||||
# Copy this file to .env and update with your values
|
||||
|
||||
# Database Configuration
|
||||
MYSQL_HOST=db
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_DB=stupa
|
||||
MYSQL_USER=stupa
|
||||
MYSQL_PASSWORD=your_secure_password_here
|
||||
MYSQL_ROOT_PASSWORD=your_secure_root_password_here
|
||||
|
||||
# Authentication
|
||||
# Master key for admin access - keep this secure!
|
||||
MASTER_KEY=your_secure_master_key_here
|
||||
|
||||
# Rate Limiting
|
||||
# Requests per minute per IP address
|
||||
RATE_IP_PER_MIN=60
|
||||
# Requests per minute per application key
|
||||
RATE_KEY_PER_MIN=30
|
||||
|
||||
# Application Settings
|
||||
# Timezone for the application
|
||||
TZ=Europe/Berlin
|
||||
|
||||
# PDF Templates (paths inside container - don't change unless you know what you're doing)
|
||||
QSM_TEMPLATE=/app/assets/qsm.pdf
|
||||
VSM_TEMPLATE=/app/assets/vsm.pdf
|
||||
|
||||
# Frontend Configuration
|
||||
NODE_ENV=production
|
||||
|
||||
# Optional: CORS Configuration (for production)
|
||||
# CORS_ORIGINS=["https://your-domain.com"]
|
||||
|
||||
# Optional: Debug Mode (never enable in production)
|
||||
# DEBUG=false
|
||||
|
||||
# Optional: Database Connection Pool
|
||||
# DB_POOL_SIZE=10
|
||||
# DB_MAX_OVERFLOW=20
|
||||
|
||||
# Optional: File Upload Limits
|
||||
# MAX_UPLOAD_SIZE=104857600 # 100MB in bytes
|
||||
# MAX_ATTACHMENTS_PER_APP=30
|
||||
|
||||
# Optional: Session Configuration
|
||||
# SESSION_TIMEOUT=3600 # 1 hour in seconds
|
||||
|
||||
# Optional: Email Configuration (for future notifications)
|
||||
# SMTP_HOST=smtp.example.com
|
||||
# SMTP_PORT=587
|
||||
# SMTP_USER=notifications@example.com
|
||||
# SMTP_PASSWORD=smtp_password_here
|
||||
# SMTP_FROM=noreply@example.com
|
||||
|
||||
# Optional: Logging Configuration
|
||||
# LOG_LEVEL=INFO
|
||||
# LOG_FILE=/var/log/stupa-api.log
|
||||
|
||||
# Optional: Backup Configuration
|
||||
# BACKUP_ENABLED=true
|
||||
# BACKUP_SCHEDULE="0 2 * * *" # Daily at 2 AM
|
||||
# BACKUP_RETENTION_DAYS=30
|
||||
|
||||
# Production Security Headers (uncomment for production)
|
||||
# SECURE_HEADERS=true
|
||||
# HSTS_ENABLED=true
|
||||
# CSP_ENABLED=true
|
||||
251
.gitignore
vendored
251
.gitignore
vendored
@ -1,164 +1,119 @@
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.coverage
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
.ruff_cache/
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
# Backend specific
|
||||
backend/build/
|
||||
backend/dist/
|
||||
backend/.eggs/
|
||||
backend/venv/
|
||||
backend/env/
|
||||
backend/.venv/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
# Frontend specific
|
||||
frontend/node_modules/
|
||||
frontend/dist/
|
||||
frontend/build/
|
||||
frontend/.parcel-cache/
|
||||
frontend/.next/
|
||||
frontend/.nuxt/
|
||||
frontend/.vuepress/
|
||||
frontend/.docusaurus/
|
||||
frontend/npm-debug.log*
|
||||
frontend/yarn-debug.log*
|
||||
frontend/yarn-error.log*
|
||||
frontend/lerna-debug.log*
|
||||
frontend/.pnpm-debug.log*
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
# Environment files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
# Docker
|
||||
docker-compose.override.yml
|
||||
|
||||
# Database
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
*.db
|
||||
mysql-data/
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
|
||||
# Testing
|
||||
coverage/
|
||||
.nyc_output/
|
||||
test-results/
|
||||
junit.xml
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
temp/
|
||||
*.tmp
|
||||
*.temp
|
||||
*.bak
|
||||
*.backup
|
||||
*.orig
|
||||
|
||||
# OS files
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
$RECYCLE.BIN/
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
*.lnk
|
||||
|
||||
# Documentation build
|
||||
docs/_build/
|
||||
docs/.doctrees/
|
||||
site/
|
||||
|
||||
# Backups
|
||||
backups/
|
||||
*.sql
|
||||
*.dump
|
||||
|
||||
# Project specific
|
||||
.ropeproject/
|
||||
uploads/
|
||||
downloads/
|
||||
cache/
|
||||
|
||||
# Certificates
|
||||
*.pem
|
||||
*.key
|
||||
*.crt
|
||||
*.csr
|
||||
*.der
|
||||
*.p12
|
||||
*.pfx
|
||||
|
||||
# Monitoring
|
||||
prometheus_data/
|
||||
grafana_data/
|
||||
|
||||
# PDF
|
||||
*.pdf
|
||||
|
||||
181
Makefile
Normal file
181
Makefile
Normal file
@ -0,0 +1,181 @@
|
||||
# STUPA PDF API Makefile
|
||||
# Convenient commands for development and deployment
|
||||
|
||||
.PHONY: help
|
||||
help: ## Show this help message
|
||||
@echo 'Usage: make [target]'
|
||||
@echo ''
|
||||
@echo 'Targets:'
|
||||
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-20s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
|
||||
.PHONY: install
|
||||
install: ## Initial setup - create .env and install dependencies
|
||||
@echo "Setting up STUPA PDF API..."
|
||||
@chmod +x scripts/*.sh
|
||||
@./scripts/create-env.sh
|
||||
@echo "Setup complete! Run 'make start' to begin."
|
||||
|
||||
.PHONY: start
|
||||
start: ## Start all services
|
||||
@docker compose up -d
|
||||
@echo "Services starting..."
|
||||
@echo "Frontend: http://localhost:3000"
|
||||
@echo "API: http://localhost:8000"
|
||||
@echo "API Docs: http://localhost:8000/docs"
|
||||
@echo "Database UI: http://localhost:8080"
|
||||
|
||||
.PHONY: stop
|
||||
stop: ## Stop all services
|
||||
@docker compose down
|
||||
|
||||
.PHONY: restart
|
||||
restart: ## Restart all services
|
||||
@docker compose restart
|
||||
|
||||
.PHONY: status
|
||||
status: ## Show service status
|
||||
@docker compose ps
|
||||
|
||||
.PHONY: logs
|
||||
logs: ## Show logs for all services
|
||||
@docker compose logs -f
|
||||
|
||||
.PHONY: logs-api
|
||||
logs-api: ## Show API logs
|
||||
@docker compose logs -f api
|
||||
|
||||
.PHONY: logs-frontend
|
||||
logs-frontend: ## Show frontend logs
|
||||
@docker compose logs -f frontend
|
||||
|
||||
.PHONY: logs-db
|
||||
logs-db: ## Show database logs
|
||||
@docker compose logs -f db
|
||||
|
||||
.PHONY: build
|
||||
build: ## Build all services
|
||||
@docker compose build
|
||||
|
||||
.PHONY: build-api
|
||||
build-api: ## Build API service
|
||||
@docker compose build api
|
||||
|
||||
.PHONY: build-frontend
|
||||
build-frontend: ## Build frontend service
|
||||
@docker compose build frontend
|
||||
|
||||
.PHONY: shell-api
|
||||
shell-api: ## Open shell in API container
|
||||
@docker compose exec api /bin/bash
|
||||
|
||||
.PHONY: shell-frontend
|
||||
shell-frontend: ## Open shell in frontend container
|
||||
@docker compose exec frontend /bin/sh
|
||||
|
||||
.PHONY: shell-db
|
||||
shell-db: ## Open MySQL client
|
||||
@docker compose exec db mysql -u root -p$${MYSQL_ROOT_PASSWORD} $${MYSQL_DB}
|
||||
|
||||
.PHONY: migrate
|
||||
migrate: ## Run database migrations
|
||||
@echo "Running database migrations..."
|
||||
@./scripts/dev.sh migrate
|
||||
|
||||
.PHONY: test
|
||||
test: ## Run all tests
|
||||
@echo "Running tests..."
|
||||
@./scripts/dev.sh test
|
||||
|
||||
.PHONY: lint
|
||||
lint: ## Run linters
|
||||
@echo "Running linters..."
|
||||
@./scripts/dev.sh lint
|
||||
|
||||
.PHONY: format
|
||||
format: ## Format code
|
||||
@echo "Formatting code..."
|
||||
@./scripts/dev.sh format
|
||||
|
||||
.PHONY: clean
|
||||
clean: ## Clean up temporary files and caches
|
||||
@echo "Cleaning up..."
|
||||
@find backend -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
|
||||
@find backend -type f -name "*.pyc" -delete 2>/dev/null || true
|
||||
@docker system prune -f
|
||||
|
||||
.PHONY: reset
|
||||
reset: ## Reset everything (WARNING: deletes all data!)
|
||||
@echo "WARNING: This will delete all data!"
|
||||
@read -p "Are you sure? Type 'yes' to continue: " confirm && \
|
||||
if [ "$$confirm" = "yes" ]; then \
|
||||
docker compose down -v; \
|
||||
docker compose down --rmi local; \
|
||||
echo "Reset complete!"; \
|
||||
else \
|
||||
echo "Aborted."; \
|
||||
fi
|
||||
|
||||
.PHONY: backup
|
||||
backup: ## Create database backup
|
||||
@mkdir -p backups
|
||||
@docker compose exec -T db mysqldump -u root -p$${MYSQL_ROOT_PASSWORD} $${MYSQL_DB} > backups/backup-$$(date +%Y%m%d-%H%M%S).sql
|
||||
@echo "Backup created in backups/"
|
||||
|
||||
.PHONY: dev
|
||||
dev: ## Start in development mode with hot reload
|
||||
@docker compose -f docker-compose.yml -f docker-compose.watch.yml up
|
||||
|
||||
.PHONY: prod
|
||||
prod: ## Start in production mode
|
||||
@docker compose up -d
|
||||
|
||||
.PHONY: update
|
||||
update: ## Update dependencies
|
||||
@echo "Updating backend dependencies..."
|
||||
@cd backend && pip-compile --upgrade requirements.in -o requirements.txt
|
||||
@echo "Updating frontend dependencies..."
|
||||
@cd frontend && npm update
|
||||
|
||||
.PHONY: docs
|
||||
docs: ## Open documentation in browser
|
||||
@echo "Opening documentation..."
|
||||
@python -m webbrowser "file://$$(pwd)/docs/README.md" || open docs/README.md || xdg-open docs/README.md
|
||||
|
||||
.PHONY: api-docs
|
||||
api-docs: ## Open API documentation in browser
|
||||
@echo "Opening API documentation..."
|
||||
@python -m webbrowser "http://localhost:8000/docs" || open "http://localhost:8000/docs" || xdg-open "http://localhost:8000/docs"
|
||||
|
||||
.PHONY: check-env
|
||||
check-env: ## Check if .env file exists
|
||||
@if [ ! -f .env ]; then \
|
||||
echo "ERROR: .env file not found!"; \
|
||||
echo "Run 'make install' to create one."; \
|
||||
exit 1; \
|
||||
else \
|
||||
echo ".env file exists ✓"; \
|
||||
fi
|
||||
|
||||
.PHONY: validate
|
||||
validate: check-env ## Validate configuration
|
||||
@echo "Validating configuration..."
|
||||
@docker compose config > /dev/null
|
||||
@echo "Configuration is valid ✓"
|
||||
|
||||
.PHONY: ps
|
||||
ps: ## Show running containers
|
||||
@docker ps --filter "label=com.docker.compose.project=stupa-pdf-api"
|
||||
|
||||
.PHONY: stats
|
||||
stats: ## Show container resource usage
|
||||
@docker stats --no-stream $$(docker compose ps -q)
|
||||
|
||||
.PHONY: version
|
||||
version: ## Show version information
|
||||
@echo "STUPA PDF API Version Information:"
|
||||
@echo "Backend: Python 3.11 with FastAPI"
|
||||
@echo "Frontend: React 18 with TypeScript"
|
||||
@echo "Database: MySQL 8.0"
|
||||
@grep -m1 version frontend/package.json || echo "Frontend version not found"
|
||||
@docker --version
|
||||
@docker compose version
|
||||
273
README.md
273
README.md
@ -1,42 +1,249 @@
|
||||
# stupa-pdf-api
|
||||
# STUPA PDF API
|
||||
|
||||
## Recent Updates
|
||||
A comprehensive system for managing student parliament (STUPA) funding applications with PDF processing, form generation, and document management capabilities.
|
||||
|
||||
### Dark Mode Loading Screen Fix
|
||||
- Fixed white background issue in loading screens when using dark mode
|
||||
- The LoadingSpinner component now properly respects the theme settings
|
||||
## 🚀 Features
|
||||
|
||||
### File Attachments Feature
|
||||
- Users can now upload up to 30 attachments per application
|
||||
- Maximum total size: 100MB
|
||||
- Files are stored securely in the database
|
||||
- Full CRUD operations with progress tracking
|
||||
- See [ATTACHMENT_FEATURE.md](ATTACHMENT_FEATURE.md) for details
|
||||
- **PDF Processing**: Parse and extract structured data from uploaded PDF forms
|
||||
- **Dynamic Form Generation**: Generate filled PDF forms from application data
|
||||
- **Document Management**: Upload and manage supporting documents (up to 30 files, 100MB total)
|
||||
- **Comparison Offers**: Manage cost comparison requirements with 3-offer validation
|
||||
- **Secure Access**: Application-specific keys and master key authentication
|
||||
- **Modern UI**: React-based frontend with Material-UI components
|
||||
- **API Documentation**: Auto-generated OpenAPI/Swagger documentation
|
||||
- **Rate Limiting**: Configurable rate limits for API protection
|
||||
- **Database UI**: Integrated Adminer for database management
|
||||
|
||||
### Comparison Offers Feature
|
||||
- Each cost position now requires 3 comparison offers
|
||||
- Alternative: Users can provide a justification if offers are not applicable
|
||||
- Visual indicators show completion status
|
||||
- Offers can be linked to uploaded attachments
|
||||
- See [COMPARISON_OFFERS_FEATURE.md](COMPARISON_OFFERS_FEATURE.md) for details
|
||||
## 📋 Prerequisites
|
||||
|
||||
### Rate Limiting Configuration
|
||||
- API rate limiting is now disabled for localhost connections
|
||||
- Includes localhost (127.0.0.1) and Docker internal IPs (172.x.x.x)
|
||||
- Production deployments maintain rate limiting for external IPs
|
||||
- Docker Engine 20.10+
|
||||
- Docker Compose 2.0+
|
||||
- Git
|
||||
- 4GB RAM minimum (8GB recommended)
|
||||
- 2GB free disk space
|
||||
|
||||
## Database Migrations
|
||||
## 🏗️ Project Structure
|
||||
|
||||
Run these migrations to add the new features:
|
||||
|
||||
```bash
|
||||
# Add attachment tables
|
||||
mysql -u your_user -p your_database < src/migrations/add_attachments_tables.sql
|
||||
|
||||
# Fix attachment data column if needed
|
||||
mysql -u your_user -p your_database < src/migrations/alter_attachments_data_column.sql
|
||||
|
||||
# Add comparison offers tables
|
||||
mysql -u your_user -p your_database < src/migrations/add_comparison_offers_tables.sql
|
||||
```
|
||||
stupa-pdf-api/
|
||||
├── backend/ # FastAPI backend application
|
||||
│ ├── src/ # Source code
|
||||
│ │ ├── migrations/ # Database migrations
|
||||
│ │ ├── service_api.py # Main API application
|
||||
│ │ ├── pdf_filler.py # PDF generation
|
||||
│ │ ├── pdf_to_struct.py # PDF parsing
|
||||
│ │ └── ...
|
||||
│ ├── assets/ # PDF templates
|
||||
│ ├── requirements.txt # Python dependencies
|
||||
│ └── Dockerfile # Backend container definition
|
||||
├── frontend/ # React frontend application
|
||||
│ ├── src/ # Source code
|
||||
│ ├── public/ # Static assets
|
||||
│ ├── package.json # Node dependencies
|
||||
│ ├── nginx.conf # Nginx configuration
|
||||
│ └── Dockerfile # Frontend container definition
|
||||
├── docs/ # Documentation
|
||||
│ ├── README.md # Documentation index
|
||||
│ ├── ARCHITECTURE.md # System architecture
|
||||
│ ├── API_REFERENCE.md # API documentation
|
||||
│ └── ...
|
||||
├── scripts/ # Helper scripts
|
||||
│ ├── create-env.sh # Environment setup script
|
||||
│ └── dev.sh # Development helper script
|
||||
├── docker-compose.yml # Multi-container orchestration
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## ⚡ Quick Start
|
||||
|
||||
### 1. Clone the Repository
|
||||
```bash
|
||||
git clone https://github.com/your-org/stupa-pdf-api.git
|
||||
cd stupa-pdf-api
|
||||
```
|
||||
|
||||
### 2. Setup Environment
|
||||
```bash
|
||||
# Make scripts executable
|
||||
chmod +x scripts/*.sh
|
||||
|
||||
# Create .env file with secure defaults
|
||||
./scripts/create-env.sh
|
||||
```
|
||||
|
||||
### 3. Start Services
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### 4. Access the Application
|
||||
- **Frontend**: http://localhost:3000
|
||||
- **API Docs**: http://localhost:8000/docs
|
||||
- **Database UI**: http://localhost:8080
|
||||
|
||||
## 🛠️ Development
|
||||
|
||||
### Using the Development Script
|
||||
```bash
|
||||
# Start all services
|
||||
./scripts/dev.sh start
|
||||
|
||||
# View logs
|
||||
./scripts/dev.sh logs api
|
||||
|
||||
# Open shell in container
|
||||
./scripts/dev.sh shell frontend
|
||||
|
||||
# Run database migrations
|
||||
./scripts/dev.sh migrate
|
||||
|
||||
# More commands
|
||||
./scripts/dev.sh help
|
||||
```
|
||||
|
||||
### Manual Development Setup
|
||||
|
||||
#### Backend Development
|
||||
```bash
|
||||
cd backend
|
||||
python -m venv venv
|
||||
source venv/bin/activate # Linux/macOS
|
||||
pip install -r requirements.txt
|
||||
uvicorn src.service_api:app --reload
|
||||
```
|
||||
|
||||
#### Frontend Development
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
Comprehensive documentation is available in the `/docs` directory:
|
||||
|
||||
- [Documentation Index](./docs/README.md)
|
||||
- [Architecture Overview](./docs/ARCHITECTURE.md)
|
||||
- [Installation Guide](./docs/INSTALLATION.md)
|
||||
- [Quick Start Guide](./docs/QUICK_START.md)
|
||||
- [API Reference](./docs/API_REFERENCE.md)
|
||||
- [Development Guide](./docs/DEVELOPMENT.md)
|
||||
|
||||
## 🔧 Configuration
|
||||
|
||||
Key environment variables:
|
||||
|
||||
```env
|
||||
# Database
|
||||
MYSQL_DB=stupa
|
||||
MYSQL_USER=stupa
|
||||
MYSQL_PASSWORD=<generated>
|
||||
|
||||
# Authentication
|
||||
MASTER_KEY=<generated>
|
||||
|
||||
# Rate Limiting
|
||||
RATE_IP_PER_MIN=60
|
||||
RATE_KEY_PER_MIN=30
|
||||
|
||||
# Application
|
||||
TZ=Europe/Berlin
|
||||
```
|
||||
|
||||
See [Configuration Guide](./docs/CONFIGURATION.md) for all options.
|
||||
|
||||
## 🗄️ Database Migrations
|
||||
|
||||
Run migrations after setup:
|
||||
|
||||
```bash
|
||||
./scripts/dev.sh migrate
|
||||
```
|
||||
|
||||
Or manually:
|
||||
```bash
|
||||
docker compose exec -T db mysql -u root -p${MYSQL_ROOT_PASSWORD} ${MYSQL_DB} < backend/src/migrations/add_attachments_tables.sql
|
||||
docker compose exec -T db mysql -u root -p${MYSQL_ROOT_PASSWORD} ${MYSQL_DB} < backend/src/migrations/add_comparison_offers_tables.sql
|
||||
# ... other migrations
|
||||
```
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
./scripts/dev.sh test
|
||||
|
||||
# Run linters
|
||||
./scripts/dev.sh lint
|
||||
|
||||
# Format code
|
||||
./scripts/dev.sh format
|
||||
```
|
||||
|
||||
## 🚢 Deployment
|
||||
|
||||
See [Deployment Guide](./docs/DEPLOYMENT.md) for production deployment instructions.
|
||||
|
||||
### Basic Production Setup
|
||||
1. Use environment-specific `.env` file
|
||||
2. Enable SSL/TLS termination
|
||||
3. Configure proper domain names
|
||||
4. Set up backup procedures
|
||||
5. Monitor logs and metrics
|
||||
|
||||
## 🔒 Security
|
||||
|
||||
- Application-specific access keys
|
||||
- Master key for admin access
|
||||
- Rate limiting per IP and key
|
||||
- Input validation and sanitization
|
||||
- SQL injection prevention via ORM
|
||||
- XSS protection in React
|
||||
- Secure password hashing
|
||||
|
||||
See [Security Guide](./docs/SECURITY.md) for best practices.
|
||||
|
||||
## 📊 API Endpoints
|
||||
|
||||
Key endpoints:
|
||||
|
||||
- `POST /upload` - Upload PDF and create application
|
||||
- `GET /applications/{id}` - Get application data
|
||||
- `POST /applications/{id}/attachments` - Upload attachments
|
||||
- `GET /applications/{id}/costs/{index}/offers` - Get comparison offers
|
||||
- `PUT /applications/{id}` - Update application
|
||||
|
||||
Full API documentation available at http://localhost:8000/docs when running.
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Run tests and linting
|
||||
5. Submit a pull request
|
||||
|
||||
See [Contributing Guide](./docs/CONTRIBUTING.md) for details.
|
||||
|
||||
## 📝 License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
## 🆘 Support
|
||||
|
||||
- Check the [FAQ](./docs/FAQ.md)
|
||||
- Review [Troubleshooting Guide](./docs/TROUBLESHOOTING.md)
|
||||
- Create an issue for bugs or features
|
||||
- Contact the development team
|
||||
|
||||
## 🏆 Acknowledgments
|
||||
|
||||
- Built with FastAPI, React, and MySQL
|
||||
- PDF processing powered by PyPDF2 and ReportLab
|
||||
- UI components from Material-UI
|
||||
|
||||
---
|
||||
|
||||
**Version**: 1.0.0
|
||||
**Last Updated**: 2024
|
||||
BIN
assets/qsm.pdf
BIN
assets/qsm.pdf
Binary file not shown.
BIN
assets/vsm.pdf
BIN
assets/vsm.pdf
Binary file not shown.
@ -355,6 +355,11 @@ app = FastAPI(title="STUPA PDF API", version="1.0.0")
|
||||
def _startup():
|
||||
init_db()
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
"""Root endpoint for health checks"""
|
||||
return {"message": "STUPA PDF API"}
|
||||
|
||||
# Helper function to check if we should skip rate limiting
|
||||
def should_skip_rate_limit(ip: str = "") -> bool:
|
||||
"""Check if rate limiting should be skipped for localhost/Docker IPs"""
|
||||
66
dev.sh
66
dev.sh
@ -1,66 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Development script for running the STUPA PDF API with Docker Compose watch mode
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${GREEN}Starting STUPA PDF API in development mode with watch...${NC}"
|
||||
|
||||
# Check if Docker is running
|
||||
if ! docker info > /dev/null 2>&1; then
|
||||
echo -e "${RED}Docker is not running. Please start Docker first.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if docker compose supports watch
|
||||
if ! docker compose version | grep -q "2\.[2-9][0-9]\|2\.[1-9][0-9][0-9]"; then
|
||||
echo -e "${YELLOW}Warning: Docker Compose watch requires version 2.22.0 or higher${NC}"
|
||||
echo -e "${YELLOW}Your version: $(docker compose version)${NC}"
|
||||
fi
|
||||
|
||||
# Create .env file if it doesn't exist
|
||||
if [ ! -f .env ]; then
|
||||
echo -e "${YELLOW}Creating .env file with default values...${NC}"
|
||||
cat > .env << EOF
|
||||
# MySQL Configuration
|
||||
MYSQL_DB=stupa
|
||||
MYSQL_USER=stupa
|
||||
MYSQL_PASSWORD=secret
|
||||
MYSQL_ROOT_PASSWORD=rootsecret
|
||||
|
||||
# Application Configuration
|
||||
MASTER_KEY=change_me_in_production
|
||||
RATE_IP_PER_MIN=120
|
||||
RATE_KEY_PER_MIN=60
|
||||
|
||||
# Timezone
|
||||
TZ=Europe/Berlin
|
||||
EOF
|
||||
echo -e "${GREEN}.env file created. Please update the MASTER_KEY for production use.${NC}"
|
||||
fi
|
||||
|
||||
# Build the development image
|
||||
echo -e "${GREEN}Building development image...${NC}"
|
||||
docker compose -f docker-compose.watch.yml build
|
||||
|
||||
# Start services with watch
|
||||
echo -e "${GREEN}Starting services with file watching enabled...${NC}"
|
||||
echo -e "${YELLOW}Changes to files in ./src will automatically reload the API${NC}"
|
||||
echo -e "${YELLOW}Changes to requirements.txt will rebuild the container${NC}"
|
||||
echo -e "${YELLOW}Changes to assets will be synced immediately${NC}"
|
||||
echo ""
|
||||
echo -e "${GREEN}Services:${NC}"
|
||||
echo -e " - API: http://localhost:8000"
|
||||
echo -e " - API Docs: http://localhost:8000/docs"
|
||||
echo -e " - Adminer: http://localhost:8080"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Press Ctrl+C to stop${NC}"
|
||||
|
||||
# Run docker compose with watch
|
||||
exec docker compose -f docker-compose.watch.yml up --watch
|
||||
@ -1,5 +1,3 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
db:
|
||||
image: mysql:8.0
|
||||
@ -33,7 +31,7 @@ services:
|
||||
|
||||
api:
|
||||
build:
|
||||
context: .
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile.dev
|
||||
container_name: stupa_api
|
||||
restart: unless-stopped
|
||||
@ -67,16 +65,16 @@ services:
|
||||
develop:
|
||||
watch:
|
||||
- action: sync
|
||||
path: ./src
|
||||
path: ./backend/src
|
||||
target: /app/src
|
||||
- action: rebuild
|
||||
path: ./requirements.txt
|
||||
path: ./backend/requirements.txt
|
||||
- action: sync
|
||||
path: ./assets
|
||||
path: ./backend/assets
|
||||
target: /app/assets
|
||||
volumes:
|
||||
- ./src:/app/src
|
||||
- ./assets:/app/assets
|
||||
- ./backend/src:/app/src
|
||||
- ./backend/assets:/app/assets
|
||||
|
||||
adminer:
|
||||
image: adminer:4
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
db:
|
||||
image: mysql:8.0
|
||||
@ -33,7 +31,7 @@ services:
|
||||
|
||||
api:
|
||||
build:
|
||||
context: .
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile
|
||||
container_name: stupa_api
|
||||
restart: unless-stopped
|
||||
@ -65,6 +63,24 @@ services:
|
||||
timeout: 5s
|
||||
retries: 6
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: ./frontend
|
||||
dockerfile: Dockerfile
|
||||
container_name: stupa_frontend
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- api
|
||||
ports:
|
||||
- "3001:80"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -qO- http://localhost/ || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 6
|
||||
|
||||
adminer:
|
||||
image: adminer:4
|
||||
container_name: stupa_adminer
|
||||
|
||||
469
docs/API_REFERENCE.md
Normal file
469
docs/API_REFERENCE.md
Normal file
@ -0,0 +1,469 @@
|
||||
# API Reference
|
||||
|
||||
This document provides a comprehensive reference for all API endpoints in the STUPA PDF API system.
|
||||
|
||||
## Base URL
|
||||
|
||||
- **Development**: `http://localhost:8000`
|
||||
- **Production**: `https://your-domain.com/api`
|
||||
|
||||
## Authentication
|
||||
|
||||
The API uses two types of authentication:
|
||||
|
||||
### Application Key Authentication
|
||||
- **Header**: `X-PA-KEY`
|
||||
- **Purpose**: Access specific application data
|
||||
- **Scope**: Read/write access to single application
|
||||
|
||||
### Master Key Authentication
|
||||
- **Header**: `X-MASTER-KEY`
|
||||
- **Purpose**: Administrative access
|
||||
- **Scope**: Full access to all resources
|
||||
|
||||
### Query Parameter Authentication
|
||||
For endpoints that need to work with direct browser access (like PDF downloads), you can use:
|
||||
- **Parameter**: `key`
|
||||
- **Example**: `/applications/25-0001?format=pdf&key=your-app-key`
|
||||
|
||||
## Response Format
|
||||
|
||||
All API responses follow this general structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": { ... }, // For successful responses
|
||||
"detail": "...", // For error messages
|
||||
"status": "success" // Optional status indicator
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "Error message here"
|
||||
}
|
||||
```
|
||||
|
||||
Common HTTP status codes:
|
||||
- `200 OK` - Success
|
||||
- `201 Created` - Resource created
|
||||
- `400 Bad Request` - Invalid request data
|
||||
- `401 Unauthorized` - Missing or invalid authentication
|
||||
- `403 Forbidden` - Insufficient permissions
|
||||
- `404 Not Found` - Resource not found
|
||||
- `429 Too Many Requests` - Rate limit exceeded
|
||||
- `500 Internal Server Error` - Server error
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Health Check
|
||||
|
||||
#### GET /
|
||||
Check if the API is running.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"message": "STUPA PDF API"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### PDF Upload and Processing
|
||||
|
||||
#### POST /upload
|
||||
Upload a PDF form and create a new application.
|
||||
|
||||
**Request:**
|
||||
- **Content-Type**: `multipart/form-data`
|
||||
- **Body**:
|
||||
- `file` (required): PDF file to upload
|
||||
- `form_type` (required): Type of form (`"pa_form"` or `"vsm_form"`)
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"pa_id": "25-0001",
|
||||
"pa_key": "generated-secure-key",
|
||||
"data": {
|
||||
"projectBeginn": "2025-01-01",
|
||||
"name": "Student Project",
|
||||
// ... other parsed data
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/upload" \
|
||||
-F "file=@application.pdf" \
|
||||
-F "form_type=pa_form"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Application Management
|
||||
|
||||
#### GET /applications/{pa_id}
|
||||
Retrieve application data.
|
||||
|
||||
**Parameters:**
|
||||
- `pa_id` (path): Application ID (e.g., "25-0001")
|
||||
- `format` (query): Response format (`"json"` or `"pdf"`)
|
||||
- `key` (query): Application key (alternative to header)
|
||||
|
||||
**Headers:**
|
||||
- `X-PA-KEY` or `X-MASTER-KEY`
|
||||
|
||||
**Response (JSON):**
|
||||
```json
|
||||
{
|
||||
"pa_id": "25-0001",
|
||||
"created_at": "2024-01-01T00:00:00",
|
||||
"updated_at": "2024-01-01T00:00:00",
|
||||
"data": {
|
||||
"projectBeginn": "2025-01-01",
|
||||
"name": "Student Project",
|
||||
// ... full application data
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response (PDF):**
|
||||
Binary PDF file with filled form.
|
||||
|
||||
---
|
||||
|
||||
#### PUT /applications/{pa_id}
|
||||
Update application data.
|
||||
|
||||
**Parameters:**
|
||||
- `pa_id` (path): Application ID
|
||||
|
||||
**Headers:**
|
||||
- `X-PA-KEY` or `X-MASTER-KEY`
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"projectBeginn": "2025-01-01",
|
||||
"name": "Updated Project Name",
|
||||
// ... fields to update
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"pa_id": "25-0001",
|
||||
"data": {
|
||||
// ... updated application data
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### DELETE /applications/{pa_id}
|
||||
Delete an application (admin only).
|
||||
|
||||
**Parameters:**
|
||||
- `pa_id` (path): Application ID
|
||||
|
||||
**Headers:**
|
||||
- `X-MASTER-KEY` (required)
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"detail": "Application deleted successfully"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Attachment Management
|
||||
|
||||
#### GET /applications/{pa_id}/attachments
|
||||
List all attachments for an application.
|
||||
|
||||
**Parameters:**
|
||||
- `pa_id` (path): Application ID
|
||||
|
||||
**Headers:**
|
||||
- `X-PA-KEY` or `X-MASTER-KEY`
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"attachments": [
|
||||
{
|
||||
"id": 1,
|
||||
"filename": "receipt.pdf",
|
||||
"content_type": "application/pdf",
|
||||
"size": 102400,
|
||||
"created_at": "2024-01-01T00:00:00"
|
||||
}
|
||||
],
|
||||
"total_count": 1,
|
||||
"total_size": 102400
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### POST /applications/{pa_id}/attachments
|
||||
Upload a new attachment.
|
||||
|
||||
**Parameters:**
|
||||
- `pa_id` (path): Application ID
|
||||
|
||||
**Headers:**
|
||||
- `X-PA-KEY` or `X-MASTER-KEY`
|
||||
|
||||
**Request:**
|
||||
- **Content-Type**: `multipart/form-data`
|
||||
- **Body**:
|
||||
- `file` (required): File to upload
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"filename": "receipt.pdf",
|
||||
"content_type": "application/pdf",
|
||||
"size": 102400,
|
||||
"created_at": "2024-01-01T00:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
**Limits:**
|
||||
- Maximum 30 attachments per application
|
||||
- Maximum 100MB total size per application
|
||||
- Maximum 50MB per file
|
||||
|
||||
---
|
||||
|
||||
#### GET /applications/{pa_id}/attachments/{attachment_id}
|
||||
Download a specific attachment.
|
||||
|
||||
**Parameters:**
|
||||
- `pa_id` (path): Application ID
|
||||
- `attachment_id` (path): Attachment ID
|
||||
|
||||
**Headers:**
|
||||
- `X-PA-KEY` or `X-MASTER-KEY`
|
||||
|
||||
**Response:**
|
||||
Binary file data
|
||||
|
||||
---
|
||||
|
||||
#### DELETE /applications/{pa_id}/attachments/{attachment_id}
|
||||
Delete an attachment.
|
||||
|
||||
**Parameters:**
|
||||
- `pa_id` (path): Application ID
|
||||
- `attachment_id` (path): Attachment ID
|
||||
|
||||
**Headers:**
|
||||
- `X-PA-KEY` or `X-MASTER-KEY`
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"detail": "Attachment deleted successfully"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Comparison Offers
|
||||
|
||||
#### GET /applications/{pa_id}/costs/{cost_position_index}/offers
|
||||
Get comparison offers for a cost position.
|
||||
|
||||
**Parameters:**
|
||||
- `pa_id` (path): Application ID
|
||||
- `cost_position_index` (path): Cost position index (0-based)
|
||||
|
||||
**Headers:**
|
||||
- `X-PA-KEY` or `X-MASTER-KEY`
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"cost_position_index": 0,
|
||||
"offers": [
|
||||
{
|
||||
"id": 1,
|
||||
"supplier_name": "Supplier A",
|
||||
"amount": 1000.00,
|
||||
"description": "Standard package",
|
||||
"url": "https://supplier-a.com/quote",
|
||||
"attachment_id": null,
|
||||
"is_preferred": false,
|
||||
"created_at": "2024-01-01T00:00:00"
|
||||
}
|
||||
],
|
||||
"no_offers_required": false,
|
||||
"justification": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### POST /applications/{pa_id}/costs/{cost_position_index}/offers
|
||||
Create a new comparison offer.
|
||||
|
||||
**Parameters:**
|
||||
- `pa_id` (path): Application ID
|
||||
- `cost_position_index` (path): Cost position index
|
||||
|
||||
**Headers:**
|
||||
- `X-PA-KEY` or `X-MASTER-KEY`
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"supplier_name": "Supplier A",
|
||||
"amount": 1000.00,
|
||||
"description": "Standard package",
|
||||
"url": "https://supplier-a.com/quote",
|
||||
"attachment_id": null,
|
||||
"is_preferred": false
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"supplier_name": "Supplier A",
|
||||
"amount": 1000.00,
|
||||
"description": "Standard package",
|
||||
"url": "https://supplier-a.com/quote",
|
||||
"attachment_id": null,
|
||||
"is_preferred": false,
|
||||
"created_at": "2024-01-01T00:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### PUT /applications/{pa_id}/costs/{cost_position_index}/offers/{offer_id}/preferred
|
||||
Set an offer as preferred.
|
||||
|
||||
**Parameters:**
|
||||
- `pa_id` (path): Application ID
|
||||
- `cost_position_index` (path): Cost position index
|
||||
- `offer_id` (path): Offer ID
|
||||
|
||||
**Headers:**
|
||||
- `X-PA-KEY` or `X-MASTER-KEY`
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"detail": "Preferred offer updated successfully"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### DELETE /applications/{pa_id}/costs/{cost_position_index}/offers/{offer_id}
|
||||
Delete a comparison offer.
|
||||
|
||||
**Parameters:**
|
||||
- `pa_id` (path): Application ID
|
||||
- `cost_position_index` (path): Cost position index
|
||||
- `offer_id` (path): Offer ID
|
||||
|
||||
**Headers:**
|
||||
- `X-PA-KEY` or `X-MASTER-KEY`
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"detail": "Offer deleted successfully"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### PUT /applications/{pa_id}/costs/{cost_position_index}/justification
|
||||
Update justification for not requiring comparison offers.
|
||||
|
||||
**Parameters:**
|
||||
- `pa_id` (path): Application ID
|
||||
- `cost_position_index` (path): Cost position index
|
||||
|
||||
**Headers:**
|
||||
- `X-PA-KEY` or `X-MASTER-KEY`
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"no_offers_required": true,
|
||||
"justification": "This is a unique service provider with no alternatives..."
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"detail": "Justification updated successfully"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
The API implements rate limiting to prevent abuse:
|
||||
|
||||
- **Per IP**: 60 requests per minute (configurable)
|
||||
- **Per Key**: 30 requests per minute (configurable)
|
||||
- **Excluded**: localhost and Docker internal IPs
|
||||
|
||||
Rate limit headers in responses:
|
||||
- `X-RateLimit-Limit`: Maximum requests allowed
|
||||
- `X-RateLimit-Remaining`: Requests remaining
|
||||
- `X-RateLimit-Reset`: Unix timestamp when limit resets
|
||||
|
||||
---
|
||||
|
||||
## WebSocket Endpoints
|
||||
|
||||
Currently, the API does not provide WebSocket endpoints. All communication is via REST HTTP.
|
||||
|
||||
---
|
||||
|
||||
## Pagination
|
||||
|
||||
For endpoints that return lists, pagination may be implemented in future versions using:
|
||||
- `limit`: Number of items per page
|
||||
- `offset`: Starting position
|
||||
- `page`: Page number (alternative to offset)
|
||||
|
||||
---
|
||||
|
||||
## Versioning
|
||||
|
||||
The API currently does not use versioning. Future versions may implement versioning via:
|
||||
- URL path: `/v1/applications`
|
||||
- Header: `Accept: application/vnd.stupa.v1+json`
|
||||
|
||||
---
|
||||
|
||||
## OpenAPI Documentation
|
||||
|
||||
Complete OpenAPI 3.0 documentation is available at:
|
||||
- **Swagger UI**: `/docs`
|
||||
- **ReDoc**: `/redoc`
|
||||
- **OpenAPI JSON**: `/openapi.json`
|
||||
|
||||
These provide interactive documentation with the ability to test endpoints directly.
|
||||
181
docs/ARCHITECTURE.md
Normal file
181
docs/ARCHITECTURE.md
Normal file
@ -0,0 +1,181 @@
|
||||
# Architecture Overview
|
||||
|
||||
## System Architecture
|
||||
|
||||
The STUPA PDF API is built using a microservices architecture with clear separation of concerns between the frontend, backend, and database layers.
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ │ │ │ │ │
|
||||
│ Frontend │────▶│ API Gateway │────▶│ Backend │
|
||||
│ (React/TS) │ │ (Nginx) │ │ (FastAPI) │
|
||||
│ │ │ │ │ │
|
||||
└─────────────────┘ └─────────────────┘ └────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ │ │ │
|
||||
│ Database │ │ File Storage │
|
||||
│ (MySQL) │ │ (Base64 DB) │
|
||||
│ │ │ │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
### Frontend (React Application)
|
||||
- **Technology**: React 18 with TypeScript
|
||||
- **State Management**: With State
|
||||
- **UI Framework**: Material-UI
|
||||
- **Build Tool**: Vite
|
||||
- **Features**:
|
||||
- Single Page Application (SPA)
|
||||
- Responsive design
|
||||
- Real-time form validation
|
||||
- File upload management
|
||||
- PDF preview capabilities
|
||||
|
||||
### API Gateway (Nginx)
|
||||
- **Purpose**: Reverse proxy and static file serving
|
||||
- **Features**:
|
||||
- Route `/api/*` requests to backend
|
||||
- Serve static frontend assets
|
||||
- Handle CORS
|
||||
- SSL termination (production)
|
||||
- Request buffering
|
||||
- Gzip compression
|
||||
|
||||
### Backend (FastAPI)
|
||||
- **Framework**: FastAPI (Python 3.11)
|
||||
- **ORM**: SQLAlchemy
|
||||
- **PDF Processing**: PyPDF2, ReportLab
|
||||
- **Features**:
|
||||
- RESTful API design
|
||||
- Automatic API documentation
|
||||
- Request validation
|
||||
- Rate limiting
|
||||
- Authentication middleware
|
||||
- PDF parsing and generation
|
||||
|
||||
### Database (MySQL)
|
||||
- **Version**: MySQL 8.0
|
||||
- **Character Set**: utf8mb4
|
||||
- **Features**:
|
||||
- Relational data model
|
||||
- Foreign key constraints
|
||||
- Indexes for performance
|
||||
- Transaction support
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Application Creation Flow
|
||||
```
|
||||
1. User uploads PDF → Frontend
|
||||
2. Frontend sends PDF to API → POST /upload
|
||||
3. Backend parses PDF → Extracts structured data
|
||||
4. Backend creates application → Stores in database
|
||||
5. Backend returns application ID and key
|
||||
6. Frontend redirects to application view
|
||||
```
|
||||
|
||||
### PDF Generation Flow
|
||||
```
|
||||
1. User requests PDF → GET /applications/{id}?format=pdf
|
||||
2. Backend loads application data
|
||||
3. Backend fills PDF template
|
||||
4. Backend returns filled PDF
|
||||
5. Frontend displays/downloads PDF
|
||||
```
|
||||
|
||||
## Security Architecture
|
||||
|
||||
### Authentication Layers
|
||||
1. **Application Key** (`X-PA-KEY`)
|
||||
- Generated per application
|
||||
- Allows read/write access to specific application
|
||||
- Stored as SHA-256 hash
|
||||
|
||||
2. **Master Key** (`X-MASTER-KEY`)
|
||||
- Environment variable
|
||||
- Full admin access
|
||||
- Never exposed to frontend
|
||||
|
||||
### Security Features
|
||||
- Rate limiting per IP and per key
|
||||
- SQL injection prevention (ORM)
|
||||
- XSS protection (React)
|
||||
- CORS configuration
|
||||
- Input validation
|
||||
- Secure password hashing
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Core Tables
|
||||
- `applications` - Main application data
|
||||
- `application_keys` - Authentication keys
|
||||
- `attachments` - File storage (Base64)
|
||||
- `application_attachments` - Link table
|
||||
- `comparison_offers` - Cost comparison data
|
||||
- `cost_position_justifications` - Justification text
|
||||
|
||||
### Key Relationships
|
||||
- Applications ↔ Keys (1:N)
|
||||
- Applications ↔ Attachments (N:N)
|
||||
- Applications ↔ Comparison Offers (1:N)
|
||||
|
||||
## Scalability Considerations
|
||||
|
||||
### Horizontal Scaling
|
||||
- Stateless API design
|
||||
- Database connection pooling
|
||||
- Load balancer ready
|
||||
- Containerized deployment
|
||||
|
||||
### Performance Optimizations
|
||||
- Database indexes on foreign keys
|
||||
- Lazy loading of attachments
|
||||
- Efficient PDF streaming
|
||||
- Response caching headers
|
||||
- Gzip compression
|
||||
|
||||
## Development vs Production
|
||||
|
||||
### Development Environment
|
||||
- Hot reloading (frontend & backend)
|
||||
- Debug logging
|
||||
- Local file storage
|
||||
- Relaxed CORS
|
||||
- Default credentials
|
||||
|
||||
### Production Environment
|
||||
- Optimized builds
|
||||
- Error tracking
|
||||
- Cloud storage ready
|
||||
- Strict CORS
|
||||
- Secret management
|
||||
- SSL/TLS encryption
|
||||
|
||||
## Future Architecture Considerations
|
||||
|
||||
### Potential Enhancements
|
||||
1. **Microservice Separation**
|
||||
- PDF service
|
||||
- Authentication service
|
||||
- Notification service
|
||||
|
||||
2. **External Storage**
|
||||
- S3-compatible object storage
|
||||
- CDN for static assets
|
||||
|
||||
3. **Caching Layer**
|
||||
- Redis for session management
|
||||
- Application data caching
|
||||
|
||||
4. **Message Queue**
|
||||
- Async PDF generation
|
||||
- Email notifications
|
||||
|
||||
5. **Monitoring**
|
||||
- Application metrics
|
||||
- Performance monitoring
|
||||
- Error tracking
|
||||
422
docs/CONFIGURATION.md
Normal file
422
docs/CONFIGURATION.md
Normal file
@ -0,0 +1,422 @@
|
||||
# Configuration Guide
|
||||
|
||||
This guide covers all configuration options for the STUPA PDF API system.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The application is configured using environment variables. These can be set in a `.env` file in the project root or passed directly to Docker.
|
||||
|
||||
### Database Configuration
|
||||
|
||||
| Variable | Description | Default | Required |
|
||||
|----------|-------------|---------|----------|
|
||||
| `MYSQL_HOST` | MySQL server hostname | `db` | Yes |
|
||||
| `MYSQL_PORT` | MySQL server port | `3306` | Yes |
|
||||
| `MYSQL_DB` | Database name | `stupa` | Yes |
|
||||
| `MYSQL_USER` | Database user | `stupa` | Yes |
|
||||
| `MYSQL_PASSWORD` | Database password | None | Yes |
|
||||
| `MYSQL_ROOT_PASSWORD` | MySQL root password | None | Yes |
|
||||
|
||||
**Example:**
|
||||
```env
|
||||
MYSQL_HOST=db
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_DB=stupa
|
||||
MYSQL_USER=stupa
|
||||
MYSQL_PASSWORD=secure_password_here
|
||||
MYSQL_ROOT_PASSWORD=secure_root_password_here
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
| Variable | Description | Default | Required |
|
||||
|----------|-------------|---------|----------|
|
||||
| `MASTER_KEY` | Master key for admin access | None | Yes |
|
||||
|
||||
**Security Notes:**
|
||||
- Generate a strong, random master key
|
||||
- Never commit the master key to version control
|
||||
- Rotate the master key periodically
|
||||
- Use different keys for each environment
|
||||
|
||||
**Example:**
|
||||
```env
|
||||
MASTER_KEY=your_very_secure_master_key_here
|
||||
```
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
| Variable | Description | Default | Required |
|
||||
|----------|-------------|---------|----------|
|
||||
| `RATE_IP_PER_MIN` | Requests per minute per IP | `60` | No |
|
||||
| `RATE_KEY_PER_MIN` | Requests per minute per key | `30` | No |
|
||||
|
||||
**Notes:**
|
||||
- Rate limiting is disabled for localhost and Docker internal IPs
|
||||
- Adjust based on your expected traffic
|
||||
- Set to `0` to disable rate limiting (not recommended for production)
|
||||
|
||||
**Example:**
|
||||
```env
|
||||
RATE_IP_PER_MIN=60
|
||||
RATE_KEY_PER_MIN=30
|
||||
```
|
||||
|
||||
### Application Settings
|
||||
|
||||
| Variable | Description | Default | Required |
|
||||
|----------|-------------|---------|----------|
|
||||
| `TZ` | Timezone for the application | `Europe/Berlin` | No |
|
||||
| `QSM_TEMPLATE` | Path to QSM PDF template | `/app/assets/qsm.pdf` | No |
|
||||
| `VSM_TEMPLATE` | Path to VSM PDF template | `/app/assets/vsm.pdf` | No |
|
||||
|
||||
**Example:**
|
||||
```env
|
||||
TZ=Europe/Berlin
|
||||
QSM_TEMPLATE=/app/assets/qsm.pdf
|
||||
VSM_TEMPLATE=/app/assets/vsm.pdf
|
||||
```
|
||||
|
||||
### Frontend Configuration
|
||||
|
||||
| Variable | Description | Default | Required |
|
||||
|----------|-------------|---------|----------|
|
||||
| `NODE_ENV` | Node environment | `production` | No |
|
||||
| `VITE_API_URL` | API URL for frontend | `/api` | No |
|
||||
|
||||
**Example:**
|
||||
```env
|
||||
NODE_ENV=production
|
||||
VITE_API_URL=/api
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### CORS Configuration
|
||||
|
||||
For production deployments, configure CORS appropriately:
|
||||
|
||||
```python
|
||||
# In service_api.py (for reference)
|
||||
CORS_ORIGINS = os.getenv("CORS_ORIGINS", "").split(",")
|
||||
# Or hardcode in production
|
||||
CORS_ORIGINS = ["https://your-domain.com"]
|
||||
```
|
||||
|
||||
### Database Connection Pool
|
||||
|
||||
Configure connection pooling for better performance:
|
||||
|
||||
```env
|
||||
# Optional database pool settings
|
||||
DB_POOL_SIZE=10
|
||||
DB_MAX_OVERFLOW=20
|
||||
DB_POOL_TIMEOUT=30
|
||||
DB_POOL_RECYCLE=3600
|
||||
```
|
||||
|
||||
### File Upload Limits
|
||||
|
||||
Control file upload constraints:
|
||||
|
||||
```env
|
||||
# Maximum upload size in bytes (100MB)
|
||||
MAX_UPLOAD_SIZE=104857600
|
||||
# Maximum attachments per application
|
||||
MAX_ATTACHMENTS_PER_APP=30
|
||||
# Maximum size per attachment (50MB)
|
||||
MAX_ATTACHMENT_SIZE=52428800
|
||||
```
|
||||
|
||||
### Logging Configuration
|
||||
|
||||
Configure logging levels and outputs:
|
||||
|
||||
```env
|
||||
# Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
LOG_LEVEL=INFO
|
||||
# Log format: json, text
|
||||
LOG_FORMAT=json
|
||||
# Log file path (optional)
|
||||
LOG_FILE=/var/log/stupa-api.log
|
||||
# Enable access logs
|
||||
ENABLE_ACCESS_LOGS=true
|
||||
```
|
||||
|
||||
### Session Configuration
|
||||
|
||||
Control session behavior:
|
||||
|
||||
```env
|
||||
# Session timeout in seconds (1 hour)
|
||||
SESSION_TIMEOUT=3600
|
||||
# Session cookie settings
|
||||
SESSION_COOKIE_SECURE=true
|
||||
SESSION_COOKIE_HTTPONLY=true
|
||||
SESSION_COOKIE_SAMESITE=strict
|
||||
```
|
||||
|
||||
## Docker Compose Configuration
|
||||
|
||||
### Port Mapping
|
||||
|
||||
Default port configuration in `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
frontend:
|
||||
ports:
|
||||
- "3000:80" # Frontend on port 3000
|
||||
api:
|
||||
ports:
|
||||
- "8000:8000" # API on port 8000
|
||||
db:
|
||||
ports:
|
||||
- "3306:3306" # MySQL on port 3306
|
||||
adminer:
|
||||
ports:
|
||||
- "8080:8080" # Adminer on port 8080
|
||||
```
|
||||
|
||||
To change ports, modify the left side of the mapping:
|
||||
```yaml
|
||||
ports:
|
||||
- "8080:80" # Change frontend to port 8080
|
||||
```
|
||||
|
||||
### Volume Configuration
|
||||
|
||||
Persistent data storage:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
db_data: # MySQL data
|
||||
```
|
||||
|
||||
For development, you can add more volumes:
|
||||
```yaml
|
||||
volumes:
|
||||
- ./backend/src:/app/src # Hot reload for backend
|
||||
- ./frontend/src:/usr/share/nginx/html # Hot reload for frontend
|
||||
```
|
||||
|
||||
### Network Configuration
|
||||
|
||||
Services communicate on an internal Docker network. To customize:
|
||||
|
||||
```yaml
|
||||
networks:
|
||||
stupa_network:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.20.0.0/16
|
||||
```
|
||||
|
||||
## Production Configuration
|
||||
|
||||
### SSL/TLS Configuration
|
||||
|
||||
For HTTPS in production, use a reverse proxy:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name your-domain.com;
|
||||
|
||||
ssl_certificate /path/to/cert.pem;
|
||||
ssl_certificate_key /path/to/key.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3000;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://localhost:8000/;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Environment-Specific Files
|
||||
|
||||
Create environment-specific configuration:
|
||||
|
||||
```bash
|
||||
# Development
|
||||
.env.development
|
||||
|
||||
# Staging
|
||||
.env.staging
|
||||
|
||||
# Production
|
||||
.env.production
|
||||
```
|
||||
|
||||
Load the appropriate file:
|
||||
```bash
|
||||
docker compose --env-file .env.production up -d
|
||||
```
|
||||
|
||||
### Security Headers
|
||||
|
||||
Add security headers in production:
|
||||
|
||||
```env
|
||||
# Enable security headers
|
||||
SECURE_HEADERS=true
|
||||
# HSTS settings
|
||||
HSTS_ENABLED=true
|
||||
HSTS_MAX_AGE=31536000
|
||||
HSTS_INCLUDE_SUBDOMAINS=true
|
||||
# CSP settings
|
||||
CSP_ENABLED=true
|
||||
CSP_POLICY="default-src 'self'"
|
||||
```
|
||||
|
||||
### Backup Configuration
|
||||
|
||||
Configure automated backups:
|
||||
|
||||
```env
|
||||
# Enable backups
|
||||
BACKUP_ENABLED=true
|
||||
# Backup schedule (cron format)
|
||||
BACKUP_SCHEDULE="0 2 * * *"
|
||||
# Backup retention days
|
||||
BACKUP_RETENTION_DAYS=30
|
||||
# Backup destination
|
||||
BACKUP_PATH=/backups
|
||||
# S3 backup (optional)
|
||||
BACKUP_S3_BUCKET=stupa-backups
|
||||
BACKUP_S3_REGION=eu-central-1
|
||||
```
|
||||
|
||||
## Performance Tuning
|
||||
|
||||
### MySQL Configuration
|
||||
|
||||
Optimize MySQL for your workload:
|
||||
|
||||
```cnf
|
||||
# my.cnf
|
||||
[mysqld]
|
||||
innodb_buffer_pool_size = 1G
|
||||
innodb_log_file_size = 256M
|
||||
innodb_flush_method = O_DIRECT
|
||||
max_connections = 200
|
||||
```
|
||||
|
||||
### API Worker Configuration
|
||||
|
||||
Configure Uvicorn workers:
|
||||
|
||||
```env
|
||||
# Number of worker processes
|
||||
WEB_CONCURRENCY=4
|
||||
# Worker class
|
||||
WORKER_CLASS=uvicorn.workers.UvicornWorker
|
||||
# Worker timeout
|
||||
WORKER_TIMEOUT=300
|
||||
```
|
||||
|
||||
### Frontend Build Optimization
|
||||
|
||||
For production builds:
|
||||
|
||||
```env
|
||||
# Enable production optimizations
|
||||
VITE_BUILD_COMPRESS=true
|
||||
VITE_BUILD_SOURCEMAP=false
|
||||
VITE_BUILD_MINIFY=true
|
||||
```
|
||||
|
||||
## Monitoring Configuration
|
||||
|
||||
### Health Check Configuration
|
||||
|
||||
Configure health check parameters:
|
||||
|
||||
```env
|
||||
# Health check interval in seconds
|
||||
HEALTH_CHECK_INTERVAL=30
|
||||
# Health check timeout
|
||||
HEALTH_CHECK_TIMEOUT=5
|
||||
# Health check retries
|
||||
HEALTH_CHECK_RETRIES=3
|
||||
```
|
||||
|
||||
### Metrics Configuration
|
||||
|
||||
Enable metrics collection:
|
||||
|
||||
```env
|
||||
# Enable Prometheus metrics
|
||||
ENABLE_METRICS=true
|
||||
METRICS_PORT=9090
|
||||
# Enable application metrics
|
||||
COLLECT_APP_METRICS=true
|
||||
```
|
||||
|
||||
## Troubleshooting Configuration Issues
|
||||
|
||||
### Validation
|
||||
|
||||
Validate your configuration:
|
||||
|
||||
```bash
|
||||
# Check Docker Compose configuration
|
||||
docker compose config
|
||||
|
||||
# Test environment variables
|
||||
docker compose run --rm api env
|
||||
|
||||
# Verify database connection
|
||||
docker compose exec api python -c "from service_api import get_db; db = next(get_db()); print('DB OK')"
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Database Connection Failed**
|
||||
- Check `MYSQL_HOST` is correct
|
||||
- Ensure database service is healthy
|
||||
- Verify credentials match
|
||||
|
||||
2. **Rate Limiting Too Restrictive**
|
||||
- Increase `RATE_IP_PER_MIN`
|
||||
- Check if IP is excluded
|
||||
|
||||
3. **File Upload Fails**
|
||||
- Check `MAX_UPLOAD_SIZE`
|
||||
- Verify disk space
|
||||
- Check nginx client_max_body_size
|
||||
|
||||
4. **CORS Errors**
|
||||
- Configure `CORS_ORIGINS`
|
||||
- Check frontend `VITE_API_URL`
|
||||
|
||||
## Configuration Best Practices
|
||||
|
||||
1. **Use Strong Passwords**
|
||||
- Minimum 20 characters
|
||||
- Use password generator
|
||||
- Different for each environment
|
||||
|
||||
2. **Separate Environments**
|
||||
- Never use production credentials in development
|
||||
- Use different master keys
|
||||
- Isolate databases
|
||||
|
||||
3. **Version Control**
|
||||
- Commit `.env.example`
|
||||
- Never commit `.env`
|
||||
- Document all variables
|
||||
|
||||
4. **Regular Updates**
|
||||
- Review configuration quarterly
|
||||
- Update dependencies
|
||||
- Rotate credentials
|
||||
|
||||
5. **Monitoring**
|
||||
- Log configuration changes
|
||||
- Alert on failures
|
||||
- Track performance metrics
|
||||
223
docs/INSTALLATION.md
Normal file
223
docs/INSTALLATION.md
Normal file
@ -0,0 +1,223 @@
|
||||
# Installation Guide
|
||||
|
||||
This guide will walk you through installing and setting up the STUPA PDF API system on your local machine or server.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### System Requirements
|
||||
- **Operating System**: Linux, macOS, or Windows (with WSL2)
|
||||
- **Memory**: Minimum 4GB RAM (8GB recommended)
|
||||
- **Storage**: At least 2GB free disk space
|
||||
- **Network**: Internet connection for downloading dependencies
|
||||
|
||||
### Required Software
|
||||
- **Docker**: Version 20.10 or higher
|
||||
- **Docker Compose**: Version 2.0 or higher
|
||||
- **Git**: For cloning the repository
|
||||
- **Text Editor**: For editing configuration files
|
||||
|
||||
### Optional Software
|
||||
- **Make**: For using the Makefile commands
|
||||
- **Node.js**: Only if developing the frontend locally
|
||||
- **Python 3.11**: Only if developing the backend locally
|
||||
|
||||
## Installation Steps
|
||||
|
||||
### 1. Clone the Repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/your-org/stupa-pdf-api.git
|
||||
cd stupa-pdf-api
|
||||
```
|
||||
|
||||
### 2. Create Environment Configuration
|
||||
|
||||
Use the provided script to create your `.env` file:
|
||||
|
||||
```bash
|
||||
./scripts/create-env.sh
|
||||
```
|
||||
|
||||
This script will:
|
||||
- Ask for configuration values (with sensible defaults)
|
||||
- Generate secure passwords automatically
|
||||
- Create a `.env` file with all required variables
|
||||
|
||||
Alternatively, create `.env` manually:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Edit .env with your preferred editor
|
||||
```
|
||||
|
||||
### 3. Build and Start Services
|
||||
|
||||
```bash
|
||||
# Build all services
|
||||
docker compose build
|
||||
|
||||
# Start all services in detached mode
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
This will start:
|
||||
- MySQL database (port 3306)
|
||||
- Backend API (port 8000)
|
||||
- Frontend application (port 3000)
|
||||
- Adminer database UI (port 8080)
|
||||
|
||||
### 4. Initialize Database
|
||||
|
||||
The database will be automatically initialized when the MySQL container starts. To run migrations manually:
|
||||
|
||||
```bash
|
||||
# Execute migrations
|
||||
docker compose exec -T db mysql -u root -p${MYSQL_ROOT_PASSWORD} ${MYSQL_DB} < backend/src/migrations/add_comparison_offers_tables.sql
|
||||
docker compose exec -T db mysql -u root -p${MYSQL_ROOT_PASSWORD} ${MYSQL_DB} < backend/src/migrations/add_attachments_tables.sql
|
||||
docker compose exec -T db mysql -u root -p${MYSQL_ROOT_PASSWORD} ${MYSQL_DB} < backend/src/migrations/add_url_to_comparison_offers.sql
|
||||
docker compose exec -T db mysql -u root -p${MYSQL_ROOT_PASSWORD} ${MYSQL_DB} < backend/src/migrations/add_is_preferred_to_comparison_offers.sql
|
||||
```
|
||||
|
||||
### 5. Verify Installation
|
||||
|
||||
Check that all services are running:
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
All services should show as "Up" and "Healthy".
|
||||
|
||||
Test the installation:
|
||||
|
||||
```bash
|
||||
# Test API endpoint
|
||||
curl http://localhost:8000/
|
||||
|
||||
# Test frontend
|
||||
curl http://localhost:3000/
|
||||
|
||||
# You should see responses from both services
|
||||
```
|
||||
|
||||
## Post-Installation
|
||||
|
||||
### Access Points
|
||||
|
||||
After successful installation, you can access:
|
||||
|
||||
- **Frontend Application**: http://localhost:3000
|
||||
- **API Documentation**: http://localhost:8000/docs
|
||||
- **Alternative API Docs**: http://localhost:8000/redoc
|
||||
- **Database UI (Adminer)**: http://localhost:8080
|
||||
- Server: `db`
|
||||
- Username: `root` or your configured `MYSQL_USER`
|
||||
- Password: Your configured password
|
||||
- Database: Your configured `MYSQL_DB`
|
||||
|
||||
### Initial Setup
|
||||
|
||||
1. **Test PDF Upload**:
|
||||
- Navigate to http://localhost:3000
|
||||
- Upload a test PDF form
|
||||
- Verify it's processed correctly
|
||||
|
||||
2. **Create Test Application**:
|
||||
- Use the frontend to create a new application
|
||||
- Note the generated application ID and key
|
||||
|
||||
3. **Verify Database**:
|
||||
- Access Adminer at http://localhost:8080
|
||||
- Check that tables are created
|
||||
- Verify test data is stored
|
||||
|
||||
## Troubleshooting Installation
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Docker Compose Not Found
|
||||
```bash
|
||||
# Install Docker Compose V2
|
||||
docker compose version
|
||||
# If not found, update Docker Desktop or install manually
|
||||
```
|
||||
|
||||
#### Port Already in Use
|
||||
```bash
|
||||
# Find process using port (example for port 3000)
|
||||
lsof -i :3000 # macOS/Linux
|
||||
netstat -ano | findstr :3000 # Windows
|
||||
|
||||
# Change port in docker-compose.yml or stop conflicting service
|
||||
```
|
||||
|
||||
#### Permission Denied
|
||||
```bash
|
||||
# Add user to docker group (Linux)
|
||||
sudo usermod -aG docker $USER
|
||||
# Log out and back in for changes to take effect
|
||||
```
|
||||
|
||||
#### Database Connection Failed
|
||||
```bash
|
||||
# Check MySQL container logs
|
||||
docker compose logs db
|
||||
|
||||
# Verify environment variables
|
||||
docker compose config
|
||||
|
||||
# Ensure database is healthy
|
||||
docker compose ps db
|
||||
```
|
||||
|
||||
### Resetting Installation
|
||||
|
||||
To completely reset the installation:
|
||||
|
||||
```bash
|
||||
# Stop all services
|
||||
docker compose down
|
||||
|
||||
# Remove volumes (WARNING: Deletes all data)
|
||||
docker compose down -v
|
||||
|
||||
# Remove all images
|
||||
docker compose down --rmi all
|
||||
|
||||
# Start fresh
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Development Installation
|
||||
|
||||
For local development without Docker:
|
||||
|
||||
### Backend Development
|
||||
```bash
|
||||
cd backend
|
||||
python -m venv venv
|
||||
source venv/bin/activate # Linux/macOS
|
||||
# or
|
||||
venv\Scripts\activate # Windows
|
||||
|
||||
pip install -r requirements.txt
|
||||
# Set environment variables
|
||||
export MYSQL_HOST=localhost
|
||||
# ... other variables
|
||||
uvicorn src.service_api:app --reload
|
||||
```
|
||||
|
||||
### Frontend Development
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
See [Development Guide](./DEVELOPMENT.md) for more details.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Review the [Configuration Guide](./CONFIGURATION.md) for advanced settings
|
||||
- Check the [Quick Start Guide](./QUICK_START.md) to begin using the system
|
||||
- Read the [API Reference](./API_REFERENCE.md) for integration options
|
||||
204
docs/QUICK_START.md
Normal file
204
docs/QUICK_START.md
Normal file
@ -0,0 +1,204 @@
|
||||
# Quick Start Guide
|
||||
|
||||
Get up and running with STUPA PDF API in 5 minutes!
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker and Docker Compose installed
|
||||
- Git installed
|
||||
- 5 minutes of your time
|
||||
|
||||
## 🚀 Quick Setup
|
||||
|
||||
### 1. Clone and Navigate
|
||||
|
||||
```bash
|
||||
git clone https://github.com/your-org/stupa-pdf-api.git
|
||||
cd stupa-pdf-api
|
||||
```
|
||||
|
||||
### 2. Generate Configuration
|
||||
|
||||
```bash
|
||||
# Make the script executable
|
||||
chmod +x scripts/create-env.sh
|
||||
|
||||
# Run the configuration script
|
||||
./scripts/create-env.sh
|
||||
```
|
||||
|
||||
Just press Enter to accept all defaults for a quick local setup.
|
||||
|
||||
### 3. Start Everything
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Wait about 30 seconds for all services to start.
|
||||
|
||||
### 4. Verify It's Working
|
||||
|
||||
```bash
|
||||
# Check all services are running
|
||||
docker compose ps
|
||||
|
||||
# Test the API
|
||||
curl http://localhost:8000/
|
||||
|
||||
# Should return: {"message":"STUPA PDF API"}
|
||||
```
|
||||
|
||||
## 🎯 Your First Application
|
||||
|
||||
### Using the Web Interface
|
||||
|
||||
1. **Open the Frontend**
|
||||
- Navigate to http://localhost:3000
|
||||
- You'll see the STUPA PDF application interface
|
||||
|
||||
2. **Upload a PDF Form**
|
||||
- Click "Upload PDF" or drag and drop a PDF form
|
||||
- The system will parse and extract data automatically
|
||||
|
||||
3. **Review Application Data**
|
||||
- Check the extracted information
|
||||
- Edit any fields if needed
|
||||
- Add comparison offers for cost positions
|
||||
|
||||
4. **Save Your Work**
|
||||
- Note the Application ID (e.g., "25-0001")
|
||||
- Save the Application Key securely (shown once!)
|
||||
|
||||
### Using the API Directly
|
||||
|
||||
```bash
|
||||
# Upload a PDF and create application
|
||||
curl -X POST "http://localhost:8000/upload" \
|
||||
-F "file=@your-form.pdf" \
|
||||
-F "form_type=pa_form"
|
||||
|
||||
# Response includes:
|
||||
# - pa_id: Your application ID
|
||||
# - pa_key: Your access key (save this!)
|
||||
```
|
||||
|
||||
## 📝 Common Tasks
|
||||
|
||||
### View Your Application
|
||||
|
||||
```bash
|
||||
# Using the web interface
|
||||
http://localhost:3000/applications/YOUR_PA_ID?key=YOUR_PA_KEY
|
||||
|
||||
# Using the API
|
||||
curl "http://localhost:8000/applications/YOUR_PA_ID?format=json" \
|
||||
-H "X-PA-KEY: YOUR_PA_KEY"
|
||||
```
|
||||
|
||||
### Generate Filled PDF
|
||||
|
||||
```bash
|
||||
# Download filled PDF
|
||||
curl "http://localhost:8000/applications/YOUR_PA_ID?format=pdf" \
|
||||
-H "X-PA-KEY: YOUR_PA_KEY" \
|
||||
-o filled-form.pdf
|
||||
```
|
||||
|
||||
### Add Attachments
|
||||
|
||||
```bash
|
||||
# Upload an attachment
|
||||
curl -X POST "http://localhost:8000/applications/YOUR_PA_ID/attachments" \
|
||||
-H "X-PA-KEY: YOUR_PA_KEY" \
|
||||
-F "file=@receipt.pdf"
|
||||
```
|
||||
|
||||
### Manage Comparison Offers
|
||||
|
||||
Use the web interface to:
|
||||
1. Click on any cost position
|
||||
2. Add at least 3 comparison offers
|
||||
3. Select your preferred offer
|
||||
4. Save changes
|
||||
|
||||
## 🛠️ Quick Admin Tasks
|
||||
|
||||
### Access the Database
|
||||
|
||||
1. Open http://localhost:8080 (Adminer)
|
||||
2. Login with:
|
||||
- System: MySQL
|
||||
- Server: `db`
|
||||
- Username: `root`
|
||||
- Password: (from your .env file)
|
||||
- Database: `stupa`
|
||||
|
||||
### View API Documentation
|
||||
|
||||
- **Interactive Docs**: http://localhost:8000/docs
|
||||
- **Alternative Docs**: http://localhost:8000/redoc
|
||||
|
||||
### Check Logs
|
||||
|
||||
```bash
|
||||
# All services
|
||||
docker compose logs -f
|
||||
|
||||
# Specific service
|
||||
docker compose logs -f api
|
||||
docker compose logs -f frontend
|
||||
docker compose logs -f db
|
||||
```
|
||||
|
||||
## 🔧 Quick Troubleshooting
|
||||
|
||||
### Services Won't Start
|
||||
|
||||
```bash
|
||||
# Check for port conflicts
|
||||
docker compose ps
|
||||
|
||||
# Restart everything
|
||||
docker compose restart
|
||||
|
||||
# Full reset
|
||||
docker compose down
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Can't Access Frontend
|
||||
|
||||
1. Check if port 3000 is already in use
|
||||
2. Wait 30 seconds after starting services
|
||||
3. Try http://127.0.0.1:3000 instead
|
||||
|
||||
### API Returns 429 (Too Many Requests)
|
||||
|
||||
The API has rate limiting. Default: 60 requests/minute per IP.
|
||||
|
||||
### Lost Application Key
|
||||
|
||||
Application keys cannot be recovered. You'll need to:
|
||||
1. Use the master key (from .env) for admin access
|
||||
2. Or create a new application
|
||||
|
||||
## 📚 Next Steps
|
||||
|
||||
Now that you're up and running:
|
||||
|
||||
1. **Explore the Frontend**: Try all features in the web interface
|
||||
2. **Read API Docs**: Check http://localhost:8000/docs
|
||||
3. **Customize Settings**: Edit `.env` for your needs
|
||||
4. **Add Test Data**: Create sample applications
|
||||
5. **Plan Deployment**: See [Deployment Guide](./DEPLOYMENT.md)
|
||||
|
||||
## 🆘 Getting Help
|
||||
|
||||
- Check [FAQ](./FAQ.md) for common questions
|
||||
- See [Troubleshooting Guide](./TROUBLESHOOTING.md) for issues
|
||||
- Review [API Reference](./API_REFERENCE.md) for integration
|
||||
|
||||
---
|
||||
|
||||
**Congratulations!** 🎉 You now have a working STUPA PDF API system!
|
||||
98
docs/README.md
Normal file
98
docs/README.md
Normal file
@ -0,0 +1,98 @@
|
||||
# STUPA PDF API Documentation
|
||||
|
||||
Welcome to the STUPA PDF API documentation. This directory contains comprehensive documentation for the STUPA PDF API system, which handles PDF processing, form filling, and application management for student parliament funding applications.
|
||||
|
||||
## Documentation Structure
|
||||
|
||||
### 📚 Main Documentation
|
||||
|
||||
- **[Architecture Overview](./ARCHITECTURE.md)** - System architecture, components, and design decisions
|
||||
- **[API Reference](./API_REFERENCE.md)** - Complete API endpoint documentation
|
||||
- **[Frontend Guide](./FRONTEND_GUIDE.md)** - Frontend development and usage guide
|
||||
- **[Database Schema](./DATABASE_SCHEMA.md)** - Database structure and relationships
|
||||
|
||||
### 🚀 Getting Started
|
||||
|
||||
- **[Installation Guide](./INSTALLATION.md)** - Step-by-step installation instructions
|
||||
- **[Configuration Guide](./CONFIGURATION.md)** - Environment variables and configuration options
|
||||
- **[Quick Start](./QUICK_START.md)** - Get up and running quickly
|
||||
|
||||
### 💻 Development
|
||||
|
||||
- **[Development Guide](./DEVELOPMENT.md)** - Development workflow and best practices
|
||||
- **[Contributing Guide](./CONTRIBUTING.md)** - How to contribute to the project
|
||||
- **[Testing Guide](./TESTING.md)** - Testing strategies and procedures
|
||||
|
||||
### 🔧 Features
|
||||
|
||||
- **[Attachment Feature](./ATTACHMENT_FEATURE.md)** - File attachment functionality
|
||||
- **[Comparison Offers Feature](./COMPARISON_OFFERS_FEATURE.md)** - Managing comparison offers
|
||||
- **[PDF Processing](./PDF_PROCESSING.md)** - PDF generation and parsing
|
||||
|
||||
### 🚢 Deployment
|
||||
|
||||
- **[Deployment Guide](./DEPLOYMENT.md)** - Production deployment instructions
|
||||
- **[Docker Guide](./DOCKER.md)** - Docker configuration and usage
|
||||
- **[Security Guide](./SECURITY.md)** - Security best practices and considerations
|
||||
|
||||
### 🔍 Troubleshooting
|
||||
|
||||
- **[FAQ](./FAQ.md)** - Frequently asked questions
|
||||
- **[Troubleshooting Guide](./TROUBLESHOOTING.md)** - Common issues and solutions
|
||||
- **[Migration Guide](./MIGRATION.md)** - Database migration procedures
|
||||
|
||||
## Quick Links
|
||||
|
||||
- **API Base URL**: `http://localhost:8000` (development)
|
||||
- **Frontend URL**: `http://localhost:3000` (development)
|
||||
- **Database UI (Adminer)**: `http://localhost:8080` (development)
|
||||
|
||||
## Project Overview
|
||||
|
||||
The STUPA PDF API is a comprehensive system for managing student parliament funding applications. It provides:
|
||||
|
||||
- **PDF Processing**: Parse uploaded PDFs and extract structured data
|
||||
- **Form Generation**: Generate filled PDF forms from application data
|
||||
- **Application Management**: Create, read, update, and delete applications
|
||||
- **File Attachments**: Upload and manage supporting documents
|
||||
- **Comparison Offers**: Manage comparison offers for cost positions
|
||||
- **Authentication**: Secure access with application keys and master keys
|
||||
- **Rate Limiting**: Protect against abuse with configurable rate limits
|
||||
|
||||
## Technology Stack
|
||||
|
||||
### Backend
|
||||
- **FastAPI** - Modern Python web framework
|
||||
- **SQLAlchemy** - SQL toolkit and ORM
|
||||
- **MySQL** - Relational database
|
||||
- **PyPDF2** - PDF processing
|
||||
- **Python 3.11** - Programming language
|
||||
|
||||
### Frontend
|
||||
- **React 18** - UI library
|
||||
- **TypeScript** - Type-safe JavaScript
|
||||
- **Material-UI** - Component library
|
||||
- **Zustand** - State management
|
||||
- **Vite** - Build tool
|
||||
|
||||
### Infrastructure
|
||||
- **Docker** - Containerization
|
||||
- **Docker Compose** - Multi-container orchestration
|
||||
- **Nginx** - Web server and reverse proxy
|
||||
|
||||
## Getting Help
|
||||
|
||||
1. Check the relevant documentation section
|
||||
2. Search the [FAQ](./FAQ.md)
|
||||
3. Look through the [Troubleshooting Guide](./TROUBLESHOOTING.md)
|
||||
4. Create an issue in the project repository
|
||||
|
||||
## Documentation Updates
|
||||
|
||||
This documentation is actively maintained. If you find any errors or areas that need improvement, please:
|
||||
|
||||
1. Check the [Contributing Guide](./CONTRIBUTING.md)
|
||||
2. Submit a pull request with your improvements
|
||||
3. Or create an issue describing what needs to be updated
|
||||
|
||||
Last updated: 2024
|
||||
128
scripts/create-env.sh
Executable file
128
scripts/create-env.sh
Executable file
@ -0,0 +1,128 @@
|
||||
#!/bin/bash
|
||||
|
||||
# STUPA PDF API Environment Setup Script
|
||||
# This script helps create a .env file with all required configurations
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Default values
|
||||
DEFAULT_MYSQL_DB="stupa"
|
||||
DEFAULT_MYSQL_USER="stupa"
|
||||
DEFAULT_MYSQL_PORT="3306"
|
||||
DEFAULT_RATE_IP_PER_MIN="60"
|
||||
DEFAULT_RATE_KEY_PER_MIN="30"
|
||||
|
||||
echo -e "${GREEN}STUPA PDF API - Environment Configuration${NC}"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Check if .env already exists
|
||||
if [ -f .env ]; then
|
||||
echo -e "${YELLOW}Warning: .env file already exists!${NC}"
|
||||
read -p "Do you want to overwrite it? (y/N): " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Aborted."
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Function to generate random password
|
||||
generate_password() {
|
||||
openssl rand -base64 32 | tr -d "=+/" | cut -c1-25
|
||||
}
|
||||
|
||||
# Database Configuration
|
||||
echo -e "${GREEN}Database Configuration${NC}"
|
||||
echo "----------------------"
|
||||
read -p "MySQL Database name [${DEFAULT_MYSQL_DB}]: " MYSQL_DB
|
||||
MYSQL_DB=${MYSQL_DB:-$DEFAULT_MYSQL_DB}
|
||||
|
||||
read -p "MySQL Username [${DEFAULT_MYSQL_USER}]: " MYSQL_USER
|
||||
MYSQL_USER=${MYSQL_USER:-$DEFAULT_MYSQL_USER}
|
||||
|
||||
read -p "MySQL Port [${DEFAULT_MYSQL_PORT}]: " MYSQL_PORT
|
||||
MYSQL_PORT=${MYSQL_PORT:-$DEFAULT_MYSQL_PORT}
|
||||
|
||||
# Generate secure passwords
|
||||
echo ""
|
||||
echo -e "${YELLOW}Generating secure passwords...${NC}"
|
||||
MYSQL_PASSWORD=$(generate_password)
|
||||
MYSQL_ROOT_PASSWORD=$(generate_password)
|
||||
MASTER_KEY=$(generate_password)
|
||||
|
||||
echo "MySQL Password: ${MYSQL_PASSWORD}"
|
||||
echo "MySQL Root Password: ${MYSQL_ROOT_PASSWORD}"
|
||||
echo "Master Key: ${MASTER_KEY}"
|
||||
echo ""
|
||||
|
||||
# Rate Limiting Configuration
|
||||
echo -e "${GREEN}Rate Limiting Configuration${NC}"
|
||||
echo "---------------------------"
|
||||
read -p "Rate limit per IP per minute [${DEFAULT_RATE_IP_PER_MIN}]: " RATE_IP_PER_MIN
|
||||
RATE_IP_PER_MIN=${RATE_IP_PER_MIN:-$DEFAULT_RATE_IP_PER_MIN}
|
||||
|
||||
read -p "Rate limit per key per minute [${DEFAULT_RATE_KEY_PER_MIN}]: " RATE_KEY_PER_MIN
|
||||
RATE_KEY_PER_MIN=${RATE_KEY_PER_MIN:-$DEFAULT_RATE_KEY_PER_MIN}
|
||||
|
||||
# Application Configuration
|
||||
echo ""
|
||||
echo -e "${GREEN}Application Configuration${NC}"
|
||||
echo "------------------------"
|
||||
read -p "Timezone [Europe/Berlin]: " TZ
|
||||
TZ=${TZ:-"Europe/Berlin"}
|
||||
|
||||
# Create .env file
|
||||
echo ""
|
||||
echo -e "${GREEN}Creating .env file...${NC}"
|
||||
|
||||
cat > .env << EOF
|
||||
# STUPA PDF API Configuration
|
||||
# Generated on $(date)
|
||||
|
||||
# Database Configuration
|
||||
MYSQL_HOST=db
|
||||
MYSQL_PORT=${MYSQL_PORT}
|
||||
MYSQL_DB=${MYSQL_DB}
|
||||
MYSQL_USER=${MYSQL_USER}
|
||||
MYSQL_PASSWORD=${MYSQL_PASSWORD}
|
||||
MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
|
||||
|
||||
# Authentication
|
||||
MASTER_KEY=${MASTER_KEY}
|
||||
|
||||
# Rate Limiting
|
||||
RATE_IP_PER_MIN=${RATE_IP_PER_MIN}
|
||||
RATE_KEY_PER_MIN=${RATE_KEY_PER_MIN}
|
||||
|
||||
# Application Settings
|
||||
TZ=${TZ}
|
||||
|
||||
# PDF Templates (paths inside container)
|
||||
QSM_TEMPLATE=/app/assets/qsm.pdf
|
||||
VSM_TEMPLATE=/app/assets/vsm.pdf
|
||||
|
||||
# Frontend Configuration
|
||||
NODE_ENV=production
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}✓ .env file created successfully!${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Important Security Notes:${NC}"
|
||||
echo "1. Keep the .env file secure and never commit it to version control"
|
||||
echo "2. Save the passwords in a secure password manager"
|
||||
echo "3. The Master Key is used for admin access - keep it safe!"
|
||||
echo ""
|
||||
echo -e "${GREEN}Next Steps:${NC}"
|
||||
echo "1. Review the .env file and adjust values if needed"
|
||||
echo "2. Run 'docker compose up -d' to start the application"
|
||||
echo "3. Access the application at http://localhost:3000"
|
||||
echo "4. Access the API directly at http://localhost:8000"
|
||||
echo "5. Access Adminer (database UI) at http://localhost:8080"
|
||||
321
scripts/dev.sh
Executable file
321
scripts/dev.sh
Executable file
@ -0,0 +1,321 @@
|
||||
#!/bin/bash
|
||||
|
||||
# STUPA PDF API Development Helper Script
|
||||
# This script provides convenient commands for development tasks
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Project directories
|
||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
BACKEND_DIR="${PROJECT_ROOT}/backend"
|
||||
FRONTEND_DIR="${PROJECT_ROOT}/frontend"
|
||||
|
||||
# Function to display usage
|
||||
usage() {
|
||||
echo -e "${GREEN}STUPA PDF API Development Helper${NC}"
|
||||
echo "================================="
|
||||
echo ""
|
||||
echo "Usage: $0 [command] [options]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " start Start all services"
|
||||
echo " stop Stop all services"
|
||||
echo " restart Restart all services"
|
||||
echo " status Show status of all services"
|
||||
echo " logs [service] Show logs (optionally for specific service)"
|
||||
echo " build [service] Build services (optionally specific service)"
|
||||
echo " shell [service] Open shell in service container"
|
||||
echo " db Open MySQL client"
|
||||
echo " migrate Run database migrations"
|
||||
echo " reset Reset everything (WARNING: deletes data)"
|
||||
echo " test Run tests"
|
||||
echo " lint Run linters"
|
||||
echo " format Format code"
|
||||
echo " clean Clean up temporary files and caches"
|
||||
echo ""
|
||||
echo "Services: api, frontend, db, adminer"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 start # Start all services"
|
||||
echo " $0 logs api # Show API logs"
|
||||
echo " $0 shell frontend # Open shell in frontend container"
|
||||
echo " $0 db # Connect to database"
|
||||
}
|
||||
|
||||
# Check if docker compose is available
|
||||
check_docker() {
|
||||
if ! command -v docker &> /dev/null; then
|
||||
echo -e "${RED}Docker is not installed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Start services
|
||||
start_services() {
|
||||
echo -e "${GREEN}Starting all services...${NC}"
|
||||
cd "${PROJECT_ROOT}"
|
||||
docker compose up -d
|
||||
echo -e "${GREEN}Services started!${NC}"
|
||||
echo ""
|
||||
echo "Access points:"
|
||||
echo " Frontend: http://localhost:3000"
|
||||
echo " API: http://localhost:8000"
|
||||
echo " API Docs: http://localhost:8000/docs"
|
||||
echo " Adminer: http://localhost:8080"
|
||||
}
|
||||
|
||||
# Stop services
|
||||
stop_services() {
|
||||
echo -e "${YELLOW}Stopping all services...${NC}"
|
||||
cd "${PROJECT_ROOT}"
|
||||
docker compose down
|
||||
echo -e "${GREEN}Services stopped!${NC}"
|
||||
}
|
||||
|
||||
# Restart services
|
||||
restart_services() {
|
||||
echo -e "${YELLOW}Restarting all services...${NC}"
|
||||
cd "${PROJECT_ROOT}"
|
||||
docker compose restart
|
||||
echo -e "${GREEN}Services restarted!${NC}"
|
||||
}
|
||||
|
||||
# Show status
|
||||
show_status() {
|
||||
echo -e "${BLUE}Service Status:${NC}"
|
||||
cd "${PROJECT_ROOT}"
|
||||
docker compose ps
|
||||
}
|
||||
|
||||
# Show logs
|
||||
show_logs() {
|
||||
cd "${PROJECT_ROOT}"
|
||||
if [ -z "$1" ]; then
|
||||
docker compose logs -f
|
||||
else
|
||||
docker compose logs -f "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
# Build services
|
||||
build_services() {
|
||||
cd "${PROJECT_ROOT}"
|
||||
if [ -z "$1" ]; then
|
||||
echo -e "${GREEN}Building all services...${NC}"
|
||||
docker compose build
|
||||
else
|
||||
echo -e "${GREEN}Building $1...${NC}"
|
||||
docker compose build "$1"
|
||||
fi
|
||||
echo -e "${GREEN}Build complete!${NC}"
|
||||
}
|
||||
|
||||
# Open shell in container
|
||||
open_shell() {
|
||||
if [ -z "$1" ]; then
|
||||
echo -e "${RED}Please specify a service${NC}"
|
||||
echo "Available services: api, frontend, db, adminer"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "${PROJECT_ROOT}"
|
||||
case "$1" in
|
||||
api)
|
||||
docker compose exec api /bin/bash
|
||||
;;
|
||||
frontend)
|
||||
docker compose exec frontend /bin/sh
|
||||
;;
|
||||
db)
|
||||
docker compose exec db /bin/bash
|
||||
;;
|
||||
adminer)
|
||||
docker compose exec adminer /bin/sh
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Unknown service: $1${NC}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Connect to database
|
||||
connect_db() {
|
||||
echo -e "${BLUE}Connecting to database...${NC}"
|
||||
cd "${PROJECT_ROOT}"
|
||||
|
||||
# Load .env file
|
||||
if [ -f .env ]; then
|
||||
export $(cat .env | grep -v '^#' | xargs)
|
||||
fi
|
||||
|
||||
docker compose exec db mysql -u root -p${MYSQL_ROOT_PASSWORD} ${MYSQL_DB}
|
||||
}
|
||||
|
||||
# Run migrations
|
||||
run_migrations() {
|
||||
echo -e "${GREEN}Running database migrations...${NC}"
|
||||
cd "${PROJECT_ROOT}"
|
||||
|
||||
# Load .env file
|
||||
if [ -f .env ]; then
|
||||
export $(cat .env | grep -v '^#' | xargs)
|
||||
fi
|
||||
|
||||
# Find all migration files
|
||||
for migration in ${BACKEND_DIR}/src/migrations/*.sql; do
|
||||
if [ -f "$migration" ]; then
|
||||
echo -e "${BLUE}Applying $(basename "$migration")...${NC}"
|
||||
docker compose exec -T db mysql -u root -p${MYSQL_ROOT_PASSWORD} ${MYSQL_DB} < "$migration" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
echo -e "${GREEN}Migrations complete!${NC}"
|
||||
}
|
||||
|
||||
# Reset everything
|
||||
reset_all() {
|
||||
echo -e "${RED}WARNING: This will delete all data!${NC}"
|
||||
read -p "Are you sure? (yes/NO): " -r
|
||||
echo
|
||||
if [[ $REPLY == "yes" ]]; then
|
||||
cd "${PROJECT_ROOT}"
|
||||
echo -e "${YELLOW}Stopping services...${NC}"
|
||||
docker compose down -v
|
||||
echo -e "${YELLOW}Removing images...${NC}"
|
||||
docker compose down --rmi local
|
||||
echo -e "${GREEN}Reset complete!${NC}"
|
||||
else
|
||||
echo "Aborted."
|
||||
fi
|
||||
}
|
||||
|
||||
# Run tests
|
||||
run_tests() {
|
||||
echo -e "${GREEN}Running tests...${NC}"
|
||||
|
||||
# Backend tests
|
||||
if [ -d "${BACKEND_DIR}/tests" ]; then
|
||||
echo -e "${BLUE}Running backend tests...${NC}"
|
||||
cd "${PROJECT_ROOT}"
|
||||
docker compose exec api pytest
|
||||
fi
|
||||
|
||||
# Frontend tests
|
||||
if [ -f "${FRONTEND_DIR}/package.json" ]; then
|
||||
echo -e "${BLUE}Running frontend tests...${NC}"
|
||||
cd "${PROJECT_ROOT}"
|
||||
docker compose exec frontend npm test
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Tests complete!${NC}"
|
||||
}
|
||||
|
||||
# Run linters
|
||||
run_lint() {
|
||||
echo -e "${GREEN}Running linters...${NC}"
|
||||
|
||||
# Backend linting
|
||||
echo -e "${BLUE}Linting backend...${NC}"
|
||||
cd "${PROJECT_ROOT}"
|
||||
docker compose exec api flake8 src/ || true
|
||||
docker compose exec api mypy src/ || true
|
||||
|
||||
# Frontend linting
|
||||
echo -e "${BLUE}Linting frontend...${NC}"
|
||||
docker compose exec frontend npm run lint || true
|
||||
|
||||
echo -e "${GREEN}Linting complete!${NC}"
|
||||
}
|
||||
|
||||
# Format code
|
||||
format_code() {
|
||||
echo -e "${GREEN}Formatting code...${NC}"
|
||||
|
||||
# Backend formatting
|
||||
echo -e "${BLUE}Formatting backend...${NC}"
|
||||
cd "${PROJECT_ROOT}"
|
||||
docker compose exec api black src/
|
||||
docker compose exec api isort src/
|
||||
|
||||
# Frontend formatting
|
||||
echo -e "${BLUE}Formatting frontend...${NC}"
|
||||
docker compose exec frontend npm run format || true
|
||||
|
||||
echo -e "${GREEN}Formatting complete!${NC}"
|
||||
}
|
||||
|
||||
# Clean up
|
||||
clean_up() {
|
||||
echo -e "${YELLOW}Cleaning up...${NC}"
|
||||
|
||||
# Python cache
|
||||
find "${BACKEND_DIR}" -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
|
||||
find "${BACKEND_DIR}" -type f -name "*.pyc" -delete 2>/dev/null || true
|
||||
|
||||
# Node modules (if you want to clean them)
|
||||
# rm -rf "${FRONTEND_DIR}/node_modules"
|
||||
|
||||
# Docker cleanup
|
||||
docker system prune -f
|
||||
|
||||
echo -e "${GREEN}Cleanup complete!${NC}"
|
||||
}
|
||||
|
||||
# Main script logic
|
||||
check_docker
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start_services
|
||||
;;
|
||||
stop)
|
||||
stop_services
|
||||
;;
|
||||
restart)
|
||||
restart_services
|
||||
;;
|
||||
status)
|
||||
show_status
|
||||
;;
|
||||
logs)
|
||||
show_logs "$2"
|
||||
;;
|
||||
build)
|
||||
build_services "$2"
|
||||
;;
|
||||
shell)
|
||||
open_shell "$2"
|
||||
;;
|
||||
db)
|
||||
connect_db
|
||||
;;
|
||||
migrate)
|
||||
run_migrations
|
||||
;;
|
||||
reset)
|
||||
reset_all
|
||||
;;
|
||||
test)
|
||||
run_tests
|
||||
;;
|
||||
lint)
|
||||
run_lint
|
||||
;;
|
||||
format)
|
||||
format_code
|
||||
;;
|
||||
clean)
|
||||
clean_up
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
@ -1,32 +0,0 @@
|
||||
-- Migration: Add attachment tables
|
||||
-- Description: Add tables for storing file attachments for applications
|
||||
-- Date: 2024
|
||||
|
||||
-- Create attachments table to store file data
|
||||
CREATE TABLE IF NOT EXISTS `attachments` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`filename` VARCHAR(255) NOT NULL,
|
||||
`content_type` VARCHAR(100) NOT NULL,
|
||||
`size` INT NOT NULL,
|
||||
`data` LONGTEXT NOT NULL, -- Base64 encoded file data
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_created_at` (`created_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Create junction table to link applications and attachments
|
||||
CREATE TABLE IF NOT EXISTS `application_attachments` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`application_id` INT NOT NULL,
|
||||
`attachment_id` INT NOT NULL,
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uq_app_attachment` (`application_id`, `attachment_id`),
|
||||
INDEX `idx_application_id` (`application_id`),
|
||||
INDEX `idx_attachment_id` (`attachment_id`),
|
||||
INDEX `idx_created_at` (`created_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Note: Foreign key constraints are not added because the Application table
|
||||
-- doesn't have a direct foreign key relationship setup in the current schema.
|
||||
-- The application_id references the id column in the applications table.
|
||||
@ -1,41 +0,0 @@
|
||||
-- Migration: Add comparison offers tables
|
||||
-- Description: Add tables for storing comparison offers for cost positions
|
||||
-- Date: 2024
|
||||
|
||||
-- Create comparison offers table
|
||||
CREATE TABLE IF NOT EXISTS `comparison_offers` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`application_id` INT NOT NULL,
|
||||
`cost_position_index` INT NOT NULL,
|
||||
`supplier_name` VARCHAR(255) NOT NULL,
|
||||
`amount` INT NOT NULL COMMENT 'Amount in cents',
|
||||
`description` TEXT,
|
||||
`attachment_id` INT,
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uq_offer_supplier` (`application_id`, `cost_position_index`, `supplier_name`),
|
||||
INDEX `idx_application_id` (`application_id`),
|
||||
INDEX `idx_cost_position` (`application_id`, `cost_position_index`),
|
||||
INDEX `idx_attachment_id` (`attachment_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Create cost position justifications table
|
||||
CREATE TABLE IF NOT EXISTS `cost_position_justifications` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`application_id` INT NOT NULL,
|
||||
`cost_position_index` INT NOT NULL,
|
||||
`no_offers_required` INT NOT NULL DEFAULT 0 COMMENT 'Boolean: 1 if no offers are required',
|
||||
`justification` TEXT COMMENT 'Required when no_offers_required is 1',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uq_position_justification` (`application_id`, `cost_position_index`),
|
||||
INDEX `idx_application_id` (`application_id`),
|
||||
INDEX `idx_no_offers` (`no_offers_required`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Note: Foreign key constraints are not added because the Application table
|
||||
-- doesn't have a direct foreign key relationship setup in the current schema.
|
||||
-- The application_id references the id column in the applications table.
|
||||
-- The attachment_id references the id column in the attachments table.
|
||||
@ -1,10 +0,0 @@
|
||||
-- Add is_preferred column to comparison_offers table
|
||||
ALTER TABLE comparison_offers
|
||||
ADD COLUMN is_preferred BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
-- Add index for efficient queries
|
||||
CREATE INDEX idx_comparison_offers_preferred
|
||||
ON comparison_offers(application_id, cost_position_index, is_preferred);
|
||||
|
||||
-- Ensure only one offer per cost position can be preferred
|
||||
-- This is enforced at the application level
|
||||
@ -1,14 +0,0 @@
|
||||
-- Migration: Add URL column to comparison_offers table
|
||||
-- Description: Add optional URL field to store links to online offers
|
||||
-- Date: 2024
|
||||
|
||||
-- Add URL column to comparison_offers table
|
||||
ALTER TABLE `comparison_offers`
|
||||
ADD COLUMN `url` VARCHAR(500) DEFAULT NULL COMMENT 'Optional URL to offer document or webpage'
|
||||
AFTER `description`;
|
||||
|
||||
-- Add index for URL column for better search performance
|
||||
CREATE INDEX `idx_url` ON `comparison_offers` (`url`);
|
||||
|
||||
-- Update existing constraint to ensure either URL or attachment is provided
|
||||
-- Note: This is handled at application level since MySQL doesn't support complex CHECK constraints
|
||||
@ -1,10 +0,0 @@
|
||||
-- Migration: Alter attachments table data column to LONGTEXT
|
||||
-- Description: Change data column from TEXT to LONGTEXT to support larger files
|
||||
-- Date: 2024
|
||||
|
||||
-- Check if the attachments table exists and alter the data column
|
||||
ALTER TABLE `attachments`
|
||||
MODIFY COLUMN `data` LONGTEXT NOT NULL COMMENT 'Base64 encoded file data';
|
||||
|
||||
-- This migration fixes the issue where TEXT type (max 65,535 bytes) was too small
|
||||
-- for Base64 encoded files. LONGTEXT supports up to 4GB of data.
|
||||
Loading…
Reference in New Issue
Block a user