feat: add FastAPI backend with PostgreSQL and start script fixes
- Add server/ directory with FastAPI backend - Fix server/start.sh to properly handle venv on Windows/Git Bash - Add alembic migrations and pyproject.toml - Add server tests Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
227
server/start.sh
Normal file
227
server/start.sh
Normal file
@@ -0,0 +1,227 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
ROOT_ENV_FILE="$ROOT_DIR/.env"
|
||||
ROOT_ENV_EXAMPLE_FILE="$ROOT_DIR/.env.example"
|
||||
VENV_DIR="$SCRIPT_DIR/.venv"
|
||||
MODE="${1:-start}"
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }
|
||||
|
||||
if [ ! -f "$ROOT_ENV_FILE" ]; then
|
||||
if [ -f "$ROOT_ENV_EXAMPLE_FILE" ]; then
|
||||
warn "Root .env not found. Creating it from .env.example"
|
||||
cp "$ROOT_ENV_EXAMPLE_FILE" "$ROOT_ENV_FILE"
|
||||
else
|
||||
error "Root .env and .env.example are both missing."
|
||||
fi
|
||||
fi
|
||||
|
||||
set -a
|
||||
. "$ROOT_ENV_FILE"
|
||||
set +a
|
||||
|
||||
SERVER_HOST="${SERVER_HOST:-127.0.0.1}"
|
||||
SERVER_PORT="${SERVER_PORT:-8000}"
|
||||
|
||||
is_wsl() {
|
||||
grep -qi microsoft /proc/version 2>/dev/null
|
||||
}
|
||||
|
||||
is_windows_mount() {
|
||||
case "$SCRIPT_DIR" in
|
||||
/mnt/*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
find_unix_python() {
|
||||
if command -v python >/dev/null 2>&1; then
|
||||
echo "python"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
echo "python3"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
find_windows_python() {
|
||||
if command -v py.exe >/dev/null 2>&1; then
|
||||
echo "py.exe -3"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v python.exe >/dev/null 2>&1; then
|
||||
echo "python.exe"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
venv_python_path() {
|
||||
if [ "${VENV_LAYOUT:-auto}" = "windows" ]; then
|
||||
if [ -x "$VENV_DIR/Scripts/python.exe" ]; then
|
||||
echo "$VENV_DIR/Scripts/python.exe"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "${VENV_LAYOUT:-auto}" = "unix" ]; then
|
||||
if [ -x "$VENV_DIR/bin/python" ]; then
|
||||
echo "$VENV_DIR/bin/python"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -x "$VENV_DIR/Scripts/python.exe" ]; then
|
||||
echo "$VENV_DIR/Scripts/python.exe"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -x "$VENV_DIR/bin/python" ]; then
|
||||
echo "$VENV_DIR/bin/python"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
run_bootstrap_python() {
|
||||
$PYTHON_BOOTSTRAP "$@"
|
||||
}
|
||||
|
||||
dependencies_ready() {
|
||||
"$PYTHON_BIN" -c "import fastapi, uvicorn, sqlalchemy, alembic, pydantic_settings" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
pip_ready() {
|
||||
"$PYTHON_BIN" -m pip --version >/dev/null 2>&1
|
||||
}
|
||||
|
||||
create_venv() {
|
||||
info "Creating virtual environment at ./server/.venv"
|
||||
if [ -d "$VENV_DIR" ]; then
|
||||
rm -rf "$VENV_DIR"
|
||||
fi
|
||||
run_bootstrap_python -m venv "$VENV_DIR"
|
||||
|
||||
if ! PYTHON_BIN="$(venv_python_path)"; then
|
||||
error "Virtual environment was not created successfully."
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_pip() {
|
||||
if pip_ready; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
warn "pip is missing in .venv, attempting repair"
|
||||
|
||||
if "$PYTHON_BIN" -m ensurepip --upgrade >/dev/null 2>&1 && pip_ready; then
|
||||
info "pip restored successfully"
|
||||
return 0
|
||||
fi
|
||||
|
||||
warn "Recreating .venv because pip repair failed"
|
||||
rm -rf "$VENV_DIR"
|
||||
create_venv
|
||||
|
||||
if "$PYTHON_BIN" -m ensurepip --upgrade >/dev/null 2>&1 && pip_ready; then
|
||||
info "pip restored after recreating .venv"
|
||||
return 0
|
||||
fi
|
||||
|
||||
error "pip could not be created inside .venv. Install Python with venv and pip support, then rerun the script."
|
||||
}
|
||||
|
||||
ensure_python_bootstrap() {
|
||||
if is_wsl && is_windows_mount; then
|
||||
if find_windows_python >/dev/null 2>&1; then
|
||||
PYTHON_BOOTSTRAP="$(find_windows_python)"
|
||||
VENV_LAYOUT="windows"
|
||||
info "Detected WSL on a Windows-mounted project"
|
||||
info "Using Windows Python directly from bash"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if find_unix_python >/dev/null 2>&1; then
|
||||
PYTHON_BOOTSTRAP="$(find_unix_python)"
|
||||
VENV_LAYOUT="unix"
|
||||
warn "Windows Python not found, falling back to WSL Python"
|
||||
return 0
|
||||
fi
|
||||
|
||||
error "Neither Windows Python nor WSL Python is available in PATH."
|
||||
fi
|
||||
|
||||
if ! PYTHON_BOOTSTRAP="$(find_unix_python)"; then
|
||||
error "Python is not installed or not available in PATH. Install Python 3.11+ first so the script can create server/.venv automatically."
|
||||
fi
|
||||
|
||||
VENV_LAYOUT="unix"
|
||||
}
|
||||
|
||||
ensure_dependencies() {
|
||||
ensure_python_bootstrap
|
||||
|
||||
if ! PYTHON_BIN="$(venv_python_path)"; then
|
||||
warn "Python virtual environment not found"
|
||||
create_venv
|
||||
fi
|
||||
|
||||
ensure_pip
|
||||
|
||||
if dependencies_ready; then
|
||||
info "Server dependencies are ready."
|
||||
return 0
|
||||
fi
|
||||
|
||||
warn "Server dependencies are missing or incomplete"
|
||||
info "Running .venv Python dependency installation"
|
||||
"$PYTHON_BIN" -m pip install --upgrade pip
|
||||
"$PYTHON_BIN" -m pip install -e ".[dev]"
|
||||
|
||||
if ! dependencies_ready; then
|
||||
error "Server dependencies are still incomplete after installation."
|
||||
fi
|
||||
|
||||
info "Server dependencies are ready."
|
||||
}
|
||||
|
||||
start_server() {
|
||||
info "Starting FastAPI server..."
|
||||
info "Access: http://$SERVER_HOST:$SERVER_PORT"
|
||||
echo ""
|
||||
|
||||
exec "$PYTHON_BIN" -m uvicorn app.main:app --reload --app-dir src --host "$SERVER_HOST" --port "$SERVER_PORT"
|
||||
}
|
||||
|
||||
case "$MODE" in
|
||||
deps)
|
||||
ensure_dependencies
|
||||
;;
|
||||
start)
|
||||
ensure_dependencies
|
||||
start_server
|
||||
;;
|
||||
*)
|
||||
error "Unknown mode: $MODE. Use one of: deps, start"
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user