commit
b787541de8
128
Makefile
Normal file
128
Makefile
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
.PHONY: help build migrate migrate-create start dev stop logs clean test lint format check-db reset-db shell
|
||||||
|
|
||||||
|
# Default target
|
||||||
|
help: ## Show this help message
|
||||||
|
@echo "Available commands:"
|
||||||
|
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
||||||
|
|
||||||
|
# Docker build commands
|
||||||
|
build: ## Build Docker containers
|
||||||
|
docker compose build
|
||||||
|
|
||||||
|
# Database commands
|
||||||
|
migrate: build ## Run database migrations in Docker
|
||||||
|
docker compose run --rm backend python -m app.db.init
|
||||||
|
|
||||||
|
migrate-create: ## Create a new migration (use NAME=migration_name)
|
||||||
|
@if [ -z "$(NAME)" ]; then \
|
||||||
|
echo "Usage: make migrate-create NAME=migration_name"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
docker compose run --rm backend alembic revision --autogenerate -m "$(NAME)"
|
||||||
|
|
||||||
|
migrate-upgrade: ## Upgrade to specific revision (use REV=revision_id)
|
||||||
|
@if [ -z "$(REV)" ]; then \
|
||||||
|
echo "Usage: make migrate-upgrade REV=revision_id"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
docker compose run --rm backend alembic upgrade "$(REV)"
|
||||||
|
|
||||||
|
migrate-downgrade: ## Downgrade to specific revision (use REV=revision_id)
|
||||||
|
@if [ -z "$(REV)" ]; then \
|
||||||
|
echo "Usage: make migrate-downgrade REV=revision_id"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
docker compose run --rm backend alembic downgrade "$(REV)"
|
||||||
|
|
||||||
|
migrate-history: ## Show migration history
|
||||||
|
docker compose run --rm backend alembic history
|
||||||
|
|
||||||
|
migrate-current: ## Show current migration revision
|
||||||
|
docker compose run --rm backend alembic current
|
||||||
|
|
||||||
|
check-db: ## Check database connection and status
|
||||||
|
docker compose run --rm backend python -c "from app.db.init import check_database_connection; print('✅ Database OK' if check_database_connection() else '❌ Database connection failed')"
|
||||||
|
|
||||||
|
reset-db: ## Reset database (remove and recreate)
|
||||||
|
@echo "⚠️ This will remove all data! Are you sure? (y/N): "; \
|
||||||
|
read confirm; \
|
||||||
|
if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \
|
||||||
|
docker compose down -v; \
|
||||||
|
echo "🗑️ Database reset. Run 'make migrate' to recreate."; \
|
||||||
|
else \
|
||||||
|
echo "❌ Database reset cancelled."; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Development commands
|
||||||
|
start: migrate ## Run migrations and start all services
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
dev: migrate ## Run migrations and start services with logs
|
||||||
|
docker compose up
|
||||||
|
|
||||||
|
start-no-migrate: build ## Start services without running migrations
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
dev-no-migrate: build ## Start services with logs without migrations
|
||||||
|
docker compose up
|
||||||
|
|
||||||
|
stop: ## Stop all Docker services
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
restart: stop start ## Restart all services with migrations
|
||||||
|
|
||||||
|
# Utility commands
|
||||||
|
logs: ## Show Docker container logs
|
||||||
|
docker compose logs -f
|
||||||
|
|
||||||
|
logs-backend: ## Show backend container logs only
|
||||||
|
docker compose logs -f backend
|
||||||
|
|
||||||
|
logs-frontend: ## Show frontend container logs only
|
||||||
|
docker compose logs -f frontend
|
||||||
|
|
||||||
|
shell: ## Open shell in backend Docker container
|
||||||
|
docker compose exec backend /bin/bash
|
||||||
|
|
||||||
|
shell-run: ## Run a new backend container with shell access
|
||||||
|
docker compose run --rm backend /bin/bash
|
||||||
|
|
||||||
|
# Testing and quality (run in Docker)
|
||||||
|
test: ## Run tests in Docker
|
||||||
|
docker compose run --rm backend python -m pytest
|
||||||
|
|
||||||
|
test-verbose: ## Run tests with verbose output in Docker
|
||||||
|
docker compose run --rm backend python -m pytest -v
|
||||||
|
|
||||||
|
lint: ## Run linting in Docker
|
||||||
|
docker compose run --rm backend python -m ruff check .
|
||||||
|
|
||||||
|
format: ## Format code in Docker
|
||||||
|
docker compose run --rm backend python -m ruff format .
|
||||||
|
|
||||||
|
# Cleanup commands
|
||||||
|
clean: ## Clean up Docker containers and images
|
||||||
|
docker compose down --rmi local --volumes --remove-orphans
|
||||||
|
|
||||||
|
clean-all: ## Clean up all Docker resources (containers, images, volumes)
|
||||||
|
docker compose down --rmi all --volumes --remove-orphans
|
||||||
|
docker system prune -f
|
||||||
|
|
||||||
|
# Development workflow commands
|
||||||
|
setup: build migrate ## Initial setup: build containers and run migrations
|
||||||
|
@echo "✅ Setup complete! Run 'make dev' to start the development server."
|
||||||
|
|
||||||
|
deploy-check: build lint test migrate ## Check if ready for deployment
|
||||||
|
@echo "✅ Deployment checks passed!"
|
||||||
|
|
||||||
|
status: ## Show status of Docker services
|
||||||
|
docker compose ps
|
||||||
|
|
||||||
|
# Production commands
|
||||||
|
prod-start: migrate ## Start services in production mode (detached)
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
prod-stop: ## Stop production services
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
prod-restart: prod-stop prod-start ## Restart production services
|
||||||
34
README.md
34
README.md
|
|
@ -36,40 +36,6 @@
|
||||||
|
|
||||||
Docker Compose автоматически переопределяет `ALABUGA_SQLITE_PATH=/data/app.db`, чтобы база сохранялась во внешнем volume. Для локального запуска вне Docker оставьте путь `./data/app.db` из примера.
|
Docker Compose автоматически переопределяет `ALABUGA_SQLITE_PATH=/data/app.db`, чтобы база сохранялась во внешнем volume. Для локального запуска вне Docker оставьте путь `./data/app.db` из примера.
|
||||||
|
|
||||||
## Локальная разработка backend
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
python -m venv .venv
|
|
||||||
source .venv/bin/activate
|
|
||||||
export PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1
|
|
||||||
pip install -r requirements-dev.txt
|
|
||||||
|
|
||||||
# подготовьте переменные окружения (однократно)
|
|
||||||
cp .env.example .env
|
|
||||||
|
|
||||||
# при необходимости включите в .env подтверждение почты
|
|
||||||
# ALABUGA_REQUIRE_EMAIL_CONFIRMATION=true
|
|
||||||
|
|
||||||
# база поднимется сама: при старте приложения автоматически выполняется Alembic upgrade head.
|
|
||||||
# Для ручной подготовки можно вызвать команду ниже — она прогоняет миграции и добавляет демо-данные.
|
|
||||||
cd ..
|
|
||||||
python -m scripts.seed_data
|
|
||||||
cd backend
|
|
||||||
|
|
||||||
# Запуск API
|
|
||||||
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
|
||||||
```
|
|
||||||
|
|
||||||
## Локальная разработка фронтенда
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd frontend
|
|
||||||
npm install
|
|
||||||
cp .env.example .env
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Пользовательские учётные записи (сидированные)
|
## Пользовательские учётные записи (сидированные)
|
||||||
|
|
||||||
| Роль | Email | Пароль |
|
| Роль | Email | Пароль |
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,19 @@ FROM python:3.13-slim
|
||||||
|
|
||||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
PYTHONUNBUFFERED=1 \
|
PYTHONUNBUFFERED=1 \
|
||||||
POETRY_VIRTUALENVS_CREATE=false \
|
|
||||||
PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1
|
PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends build-essential libpq-dev \
|
&& apt-get install -y --no-install-recommends build-essential libpq-dev curl \
|
||||||
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||||
|
|
||||||
COPY requirements.txt requirements.txt
|
# Install uv
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir uv
|
||||||
|
|
||||||
|
COPY pyproject.toml ./
|
||||||
|
RUN uv pip install --system --no-cache -e .
|
||||||
|
|
||||||
COPY . /app
|
COPY . /app
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
"""Add email confirmation fields to users"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '20240927_0003'
|
|
||||||
down_revision = '20240611_0002'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
op.add_column('users', sa.Column('is_email_confirmed', sa.Boolean(), nullable=False, server_default=sa.true()))
|
|
||||||
op.add_column('users', sa.Column('email_confirmation_token', sa.String(length=128), nullable=True))
|
|
||||||
op.add_column('users', sa.Column('email_confirmed_at', sa.DateTime(timezone=True), nullable=True))
|
|
||||||
op.execute('UPDATE users SET is_email_confirmed = 1')
|
|
||||||
op.alter_column('users', 'is_email_confirmed', server_default=None)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
op.drop_column('users', 'email_confirmed_at')
|
|
||||||
op.drop_column('users', 'email_confirmation_token')
|
|
||||||
op.drop_column('users', 'is_email_confirmed')
|
|
||||||
|
|
@ -7,7 +7,7 @@ import sqlalchemy as sa
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = '20240927_0004'
|
revision = '20240927_0004'
|
||||||
down_revision = '20240927_0003'
|
down_revision = '20240611_0002'
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ def register(user_in: UserRegister, db: Session = Depends(get_db)) -> Token | di
|
||||||
token = issue_confirmation_token(user, db)
|
token = issue_confirmation_token(user, db)
|
||||||
return {
|
return {
|
||||||
"detail": "Мы отправили письмо с подтверждением. Введите код, чтобы активировать аккаунт.",
|
"detail": "Мы отправили письмо с подтверждением. Введите код, чтобы активировать аккаунт.",
|
||||||
"debug_token": token if settings.environment != 'production' else None,
|
"debug_token": token if settings.debug else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
# 4. Если подтверждение выключено, сразу создаём JWT и возвращаем его фронтенду.
|
# 4. Если подтверждение выключено, сразу создаём JWT и возвращаем его фронтенду.
|
||||||
|
|
@ -110,7 +110,7 @@ def request_confirmation(payload: EmailRequest, db: Session = Depends(get_db)) -
|
||||||
token = issue_confirmation_token(user, db)
|
token = issue_confirmation_token(user, db)
|
||||||
|
|
||||||
hint = None
|
hint = None
|
||||||
if settings.environment != 'production':
|
if settings.debug:
|
||||||
hint = token
|
hint = token
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ class Settings(BaseSettings):
|
||||||
model_config = SettingsConfigDict(env_file=BASE_DIR / ".env", env_prefix="ALABUGA_", extra="ignore")
|
model_config = SettingsConfigDict(env_file=BASE_DIR / ".env", env_prefix="ALABUGA_", extra="ignore")
|
||||||
|
|
||||||
project_name: str = "Alabuga Gamification API"
|
project_name: str = "Alabuga Gamification API"
|
||||||
environment: str = "local"
|
debug: bool = False
|
||||||
secret_key: str = "super-secret-key-change-me"
|
secret_key: str = "super-secret-key-change-me"
|
||||||
jwt_algorithm: str = "HS256"
|
jwt_algorithm: str = "HS256"
|
||||||
access_token_expire_minutes: int = 60 * 12
|
access_token_expire_minutes: int = 60 * 12
|
||||||
|
|
|
||||||
71
backend/app/db/init.py
Normal file
71
backend/app/db/init.py
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
"""Database initialization and migration utilities."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from alembic import command
|
||||||
|
from alembic.config import Config
|
||||||
|
from sqlalchemy import text
|
||||||
|
|
||||||
|
from app.core.config import settings
|
||||||
|
from app.db.session import engine
|
||||||
|
|
||||||
|
ALEMBIC_CONFIG = Path(__file__).resolve().parents[2] / "alembic.ini"
|
||||||
|
|
||||||
|
|
||||||
|
def get_alembic_config() -> Config:
|
||||||
|
"""Get configured Alembic Config object."""
|
||||||
|
config = Config(str(ALEMBIC_CONFIG))
|
||||||
|
config.set_main_option("sqlalchemy.url", str(settings.database_url))
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def check_database_connection() -> bool:
|
||||||
|
"""Check if database connection is working."""
|
||||||
|
try:
|
||||||
|
with engine.connect() as conn:
|
||||||
|
conn.execute(text("SELECT 1"))
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Database connection failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations() -> bool:
|
||||||
|
"""Run database migrations to head."""
|
||||||
|
try:
|
||||||
|
config = get_alembic_config()
|
||||||
|
command.upgrade(config, "head")
|
||||||
|
print("✅ Database migrations completed successfully")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Migration failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def init_database() -> bool:
|
||||||
|
"""Initialize database: check connection and run migrations."""
|
||||||
|
print("🔄 Initializing database...")
|
||||||
|
|
||||||
|
if not check_database_connection():
|
||||||
|
print("❌ Cannot connect to database")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not run_migrations():
|
||||||
|
print("❌ Failed to run migrations")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print("✅ Database initialization completed successfully")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""CLI entry point for database initialization."""
|
||||||
|
success = init_database()
|
||||||
|
sys.exit(0 if success else 1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -2,20 +2,74 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from alembic import command
|
|
||||||
from alembic.config import Config
|
|
||||||
from alembic.script import ScriptDirectory
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from sqlalchemy import inspect, text
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.api.routes import admin, auth, journal, missions, onboarding, store, users
|
from app.api.routes import admin, auth, journal, missions, onboarding, store, users
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.db.session import engine
|
from app.core.security import get_password_hash
|
||||||
|
from app.db.session import SessionLocal
|
||||||
|
from app.models.user import User, UserRole
|
||||||
|
from app.models.rank import Rank
|
||||||
|
# Import all models to ensure they're registered with Base.metadata
|
||||||
|
from app import models # This imports all models through the __init__.py
|
||||||
|
|
||||||
|
app = FastAPI(title=settings.project_name)
|
||||||
|
|
||||||
|
|
||||||
|
def create_demo_users() -> None:
|
||||||
|
"""Create demo users if they don't exist."""
|
||||||
|
session: Session = SessionLocal()
|
||||||
|
try:
|
||||||
|
# Check if demo users already exist
|
||||||
|
pilot_exists = session.query(User).filter(User.email == "candidate@alabuga.space").first()
|
||||||
|
hr_exists = session.query(User).filter(User.email == "hr@alabuga.space").first()
|
||||||
|
|
||||||
|
if pilot_exists and hr_exists:
|
||||||
|
print("✅ Demo users already exist")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get base rank (or None if no ranks exist)
|
||||||
|
base_rank = session.query(Rank).order_by(Rank.required_xp).first()
|
||||||
|
|
||||||
|
# Create pilot demo user
|
||||||
|
if not pilot_exists:
|
||||||
|
pilot = User(
|
||||||
|
email="candidate@alabuga.space",
|
||||||
|
full_name="Алексей Пилотов",
|
||||||
|
role=UserRole.PILOT,
|
||||||
|
hashed_password=get_password_hash("orbita123"),
|
||||||
|
current_rank_id=base_rank.id if base_rank else None,
|
||||||
|
is_email_confirmed=True,
|
||||||
|
preferred_branch="Получение оффера",
|
||||||
|
motivation="Хочу пройти все миссии и закрепиться в экипаже.",
|
||||||
|
)
|
||||||
|
session.add(pilot)
|
||||||
|
print("✅ Created demo pilot user: candidate@alabuga.space / orbita123")
|
||||||
|
|
||||||
|
# Create HR demo user
|
||||||
|
if not hr_exists:
|
||||||
|
hr_rank = session.query(Rank).order_by(Rank.required_xp.desc()).first()
|
||||||
|
hr = User(
|
||||||
|
email="hr@alabuga.space",
|
||||||
|
full_name="Мария HR",
|
||||||
|
role=UserRole.HR,
|
||||||
|
hashed_password=get_password_hash("orbita123"),
|
||||||
|
current_rank_id=hr_rank.id if hr_rank else None,
|
||||||
|
is_email_confirmed=True,
|
||||||
|
preferred_branch="Куратор миссий",
|
||||||
|
)
|
||||||
|
session.add(hr)
|
||||||
|
print("✅ Created demo HR user: hr@alabuga.space / orbita123")
|
||||||
|
|
||||||
|
session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Failed to create demo users: {e}")
|
||||||
|
session.rollback()
|
||||||
|
finally:
|
||||||
|
session.close()
|
||||||
|
|
||||||
ALEMBIC_CONFIG = Path(__file__).resolve().parents[1] / "alembic.ini"
|
|
||||||
|
|
||||||
app = FastAPI(title=settings.project_name)
|
app = FastAPI(title=settings.project_name)
|
||||||
|
|
||||||
|
|
@ -29,51 +83,6 @@ app.add_middleware(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def run_migrations() -> None:
|
|
||||||
"""Гарантируем, что база обновлена до последней схемы Alembic."""
|
|
||||||
|
|
||||||
config = Config(str(ALEMBIC_CONFIG))
|
|
||||||
config.set_main_option("sqlalchemy.url", str(settings.database_url))
|
|
||||||
inspector = inspect(engine)
|
|
||||||
script = ScriptDirectory.from_config(config)
|
|
||||||
head_revision = script.get_current_head()
|
|
||||||
|
|
||||||
tables = inspector.get_table_names()
|
|
||||||
current_revision: str | None = None
|
|
||||||
|
|
||||||
if "alembic_version" in tables:
|
|
||||||
with engine.begin() as conn:
|
|
||||||
row = conn.execute(text("SELECT version_num FROM alembic_version LIMIT 1")).fetchone()
|
|
||||||
current_revision = row[0] if row else None
|
|
||||||
|
|
||||||
if "alembic_version" not in tables or current_revision is None:
|
|
||||||
if tables:
|
|
||||||
# База создана через Base.metadata.create_all. Добавляем отсутствующие поля вручную
|
|
||||||
# и фиксируем версию как актуальную, чтобы последующие миграции применялись штатно.
|
|
||||||
user_columns = {col["name"] for col in inspector.get_columns("users")}
|
|
||||||
with engine.begin() as conn:
|
|
||||||
if "preferred_branch" not in user_columns:
|
|
||||||
conn.execute(text("ALTER TABLE users ADD COLUMN preferred_branch VARCHAR(160)"))
|
|
||||||
if "motivation" not in user_columns:
|
|
||||||
conn.execute(text("ALTER TABLE users ADD COLUMN motivation TEXT"))
|
|
||||||
conn.execute(text("CREATE TABLE IF NOT EXISTS alembic_version (version_num VARCHAR(32) NOT NULL)"))
|
|
||||||
conn.execute(text("DELETE FROM alembic_version"))
|
|
||||||
conn.execute(text("INSERT INTO alembic_version (version_num) VALUES (:rev)"), {"rev": head_revision})
|
|
||||||
return
|
|
||||||
# Таблиц ещё нет — создадим их миграциями.
|
|
||||||
command.upgrade(config, "head")
|
|
||||||
return
|
|
||||||
|
|
||||||
command.upgrade(config, "head")
|
|
||||||
|
|
||||||
|
|
||||||
@app.on_event("startup")
|
|
||||||
def on_startup() -> None:
|
|
||||||
"""Прогоняем миграции перед обработкой запросов."""
|
|
||||||
|
|
||||||
run_migrations()
|
|
||||||
|
|
||||||
|
|
||||||
app.include_router(auth.router)
|
app.include_router(auth.router)
|
||||||
app.include_router(users.router)
|
app.include_router(users.router)
|
||||||
app.include_router(missions.router)
|
app.include_router(missions.router)
|
||||||
|
|
@ -83,8 +92,15 @@ app.include_router(store.router)
|
||||||
app.include_router(admin.router)
|
app.include_router(admin.router)
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_event("startup")
|
||||||
|
async def startup_event():
|
||||||
|
"""Create demo users on startup if in debug mode."""
|
||||||
|
if settings.debug:
|
||||||
|
create_demo_users()
|
||||||
|
|
||||||
|
|
||||||
@app.get("/", summary="Проверка работоспособности")
|
@app.get("/", summary="Проверка работоспособности")
|
||||||
def healthcheck() -> dict[str, str]:
|
def healthcheck() -> dict[str, str]:
|
||||||
"""Простой ответ для Docker healthcheck."""
|
"""Простой ответ для Docker healthcheck."""
|
||||||
|
|
||||||
return {"status": "ok", "environment": settings.environment}
|
return {"status": "ok"}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,33 @@ name = "alabuga-backend"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Геймифицированный модуль платформы 'Алабуга'"
|
description = "Геймифицированный модуль платформы 'Алабуга'"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
dependencies = []
|
dependencies = [
|
||||||
|
"fastapi==0.111.0",
|
||||||
|
"uvicorn[standard]==0.30.1",
|
||||||
|
"SQLAlchemy>=2.0.36,<3",
|
||||||
|
"alembic>=1.14.0,<2",
|
||||||
|
"pydantic==2.9.2",
|
||||||
|
"pydantic-settings==2.10.1",
|
||||||
|
"passlib[bcrypt]==1.7.4",
|
||||||
|
"python-jose[cryptography]==3.3.0",
|
||||||
|
"python-multipart==0.0.9",
|
||||||
|
"bcrypt==4.1.3",
|
||||||
|
"email-validator==2.1.1",
|
||||||
|
"fastapi-pagination==0.12.24",
|
||||||
|
"Jinja2==3.1.4"
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"pytest==8.2.2",
|
||||||
|
"pytest-asyncio==0.23.7",
|
||||||
|
"httpx==0.27.0",
|
||||||
|
"coverage==7.5.3",
|
||||||
|
"black==24.4.2",
|
||||||
|
"isort==5.13.2",
|
||||||
|
"ruff==0.4.7",
|
||||||
|
"mypy==1.10.0"
|
||||||
|
]
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 100
|
line-length = 100
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
-r requirements.txt
|
|
||||||
pytest==8.2.2
|
|
||||||
pytest-asyncio==0.23.7
|
|
||||||
httpx==0.27.0
|
|
||||||
coverage==7.5.3
|
|
||||||
black==24.4.2
|
|
||||||
isort==5.13.2
|
|
||||||
ruff==0.4.7
|
|
||||||
mypy==1.10.0
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
fastapi==0.111.0
|
|
||||||
uvicorn[standard]==0.30.1
|
|
||||||
SQLAlchemy>=2.0.36,<3
|
|
||||||
alembic>=1.14.0,<2
|
|
||||||
pydantic==2.9.2
|
|
||||||
pydantic-settings==2.10.1
|
|
||||||
passlib[bcrypt]==1.7.4
|
|
||||||
python-jose[cryptography]==3.3.0
|
|
||||||
python-multipart==0.0.9
|
|
||||||
bcrypt==4.1.3
|
|
||||||
email-validator==2.1.1
|
|
||||||
fastapi-pagination==0.12.24
|
|
||||||
Jinja2==3.1.4
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
version: '3.9'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
build:
|
build:
|
||||||
|
|
@ -11,10 +9,10 @@ services:
|
||||||
- backend-data:/data
|
- backend-data:/data
|
||||||
- ./backend:/app
|
- ./backend:/app
|
||||||
env_file:
|
env_file:
|
||||||
- backend/.env.example
|
- backend/.env
|
||||||
environment:
|
|
||||||
ALABUGA_ENVIRONMENT: docker
|
|
||||||
depends_on: []
|
depends_on: []
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
build:
|
build:
|
||||||
|
|
@ -22,18 +20,21 @@ services:
|
||||||
command: npm run dev -- --hostname 0.0.0.0 --port 3000
|
command: npm run dev -- --hostname 0.0.0.0 --port 3000
|
||||||
ports:
|
ports:
|
||||||
- '3000:3000'
|
- '3000:3000'
|
||||||
|
env_file:
|
||||||
|
- frontend/.env
|
||||||
environment:
|
environment:
|
||||||
NEXT_PUBLIC_API_URL: http://localhost:8000
|
|
||||||
NEXT_INTERNAL_API_URL: http://backend:8000
|
NEXT_INTERNAL_API_URL: http://backend:8000
|
||||||
NEXT_PUBLIC_DEMO_EMAIL: candidate@alabuga.space
|
|
||||||
NEXT_PUBLIC_DEMO_PASSWORD: orbita123
|
|
||||||
NEXT_PUBLIC_DEMO_HR_EMAIL: hr@alabuga.space
|
|
||||||
NEXT_PUBLIC_DEMO_HR_PASSWORD: orbita123
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./frontend:/app
|
- ./frontend:/app
|
||||||
- /app/node_modules
|
- /app/node_modules
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
backend-data:
|
backend-data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
app-network:
|
||||||
|
driver: bridge
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user