commit c88e74b029ac2ea2cc387c2af60b07e55ae863a6 Author: Toy Rik Date: Thu Dec 18 09:07:06 2025 +0300 init diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..96b25fb --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +DATABASE_URL=sqlite:///./data/app.db diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..73d4a08 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +__pycache__/ +*.pyc +*.db +.env +.DS_Store +alembic/env.py diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..52f2acb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 0000000..bf01ec2 --- /dev/null +++ b/alembic.ini @@ -0,0 +1,40 @@ +[alembic] +script_location = alembic +prepend_sys_path = . +sqlalchemy.url = sqlite:///./data/app.db + +[post_write_hooks] + +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +class = logging.Formatter diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/core/config.py b/app/core/config.py new file mode 100644 index 0000000..8db028f --- /dev/null +++ b/app/core/config.py @@ -0,0 +1,5 @@ +import os +from dotenv import load_dotenv + +DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./data/app.db") + diff --git a/app/database/session.py b/app/database/session.py new file mode 100644 index 0000000..c501002 --- /dev/null +++ b/app/database/session.py @@ -0,0 +1,14 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from app.core.config import DATABASE_URL + +connect_args = {"check_same_thread": False} if "sqlite" in DATABASE_URL else {} +engine = create_engine(DATABASE_URL, connect_args=connect_args) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..4ce0e79 --- /dev/null +++ b/app/main.py @@ -0,0 +1,12 @@ +from fastapi import FastAPI +from fastapi.staticfiles import StaticFiles +from app.database.session import engine +from app.models.user import Base +from routes import web + +# Создаём таблицы (для демо; в продакшене — Alembic) +Base.metadata.create_all(bind=engine) + +app = FastAPI(title="FastAPI + SQLite Boilerplate") +app.mount("/static", StaticFiles(directory="static"), name="static") +app.include_router(web.router) diff --git a/app/models/__init__.py b/app/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/models/user.py b/app/models/user.py new file mode 100644 index 0000000..f41344f --- /dev/null +++ b/app/models/user.py @@ -0,0 +1,9 @@ +from sqlalchemy import Column, Integer, String +from sqlalchemy.ext.declarative import declarative_base + +Base = declarative_base() + +class User(Base): + __tablename__ = "users" + id = Column(Integer, primary_key=True, index=True) + name = Column(String, index=True) diff --git a/data/.gitignore b/data/.gitignore new file mode 100644 index 0000000..b722e9e --- /dev/null +++ b/data/.gitignore @@ -0,0 +1 @@ +!.gitignore \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b7303d6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +services: + web: + build: . + ports: + - "8000:8000" + env_file: + - .env + volumes: + - ./app:/app/app + - ./templates:/app/templates + - ./static:/app/static + - ./data:/app/data + environment: + - DATABASE_URL=sqlite:///./data/app.db + restart: unless-stopped diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0724d3c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +fastapi==0.115.0 +uvicorn[standard]==0.32.0 +sqlalchemy==2.0.36 +alembic==1.13.0 +jinja2==3.1.4 +python-dotenv==1.0.1 +pytest==8.3.0 +httpx==0.27.0 diff --git a/routes/__init__.py b/routes/__init__.py new file mode 100644 index 0000000..d6194da --- /dev/null +++ b/routes/__init__.py @@ -0,0 +1 @@ +from . import web diff --git a/routes/web.py b/routes/web.py new file mode 100644 index 0000000..25a3163 --- /dev/null +++ b/routes/web.py @@ -0,0 +1,31 @@ +# routes/web.py +from fastapi import APIRouter, Request +from fastapi.templating import Jinja2Templates +from sqlalchemy import text +from app.database.session import engine + +router = APIRouter() +templates = Jinja2Templates(directory="templates") + +@router.get("/") +def home(request: Request): + db_info = {"status": "❌ Не удалось подключиться", "url": ""} + + try: + # Пробуем выполнить простой запрос + with engine.connect() as conn: + result = conn.execute(text("SELECT 1")) + if result.fetchone(): + db_info["status"] = "✅ Подключено" + db_info["url"] = str(engine.url) + except Exception as e: + db_info["status"] = f"❌ Ошибка: {str(e)[:60]}..." # Обрезаем длинные ошибки + + return templates.TemplateResponse( + "index.html", + { + "request": request, + "title": "FastAPI + SQLite", + "db_info": db_info + } + ) \ No newline at end of file diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..61e3d85 --- /dev/null +++ b/static/style.css @@ -0,0 +1,47 @@ +/* static/style.css */ +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + background: #f9fafb; + margin: 0; + padding: 2rem; + display: flex; + justify-content: center; +} +.container { + background: white; + padding: 2.5rem; + border-radius: 16px; + box-shadow: 0 10px 25px rgba(0,0,0,0.08); + max-width: 700px; + width: 100%; +} +h1 { + color: #1e40af; + text-align: center; + margin-top: 0; +} +h2 { + color: #374151; + border-bottom: 1px solid #e5e7eb; + padding-bottom: 0.5rem; + margin-top: 2rem; +} +.db-status { + background: #f0f9ff; + padding: 1.2rem; + border-radius: 10px; + margin: 1.5rem 0; + border-left: 4px solid #3b82f6; +} +.info { + margin-top: 2rem; + text-align: center; + color: #6b7280; +} +code { + background: #f3f4f6; + padding: 0.2rem 0.4rem; + border-radius: 4px; + font-family: monospace; + font-size: 0.9em; +} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..d447d3c --- /dev/null +++ b/templates/index.html @@ -0,0 +1,27 @@ + + + + + + {{ title }} + + + +
+

🧱 FastAPI + SQLite

+ +
+

🗄 Состояние базы данных

+

Статус: {{ db_info.status | safe }}

+ {% if db_info.url %} +

База: {{ db_info.url }}

+ {% endif %} +
+ +
+

📁 Архитектура: всё по папкам (как в Laravel)

+

🐳 Запущено в Docker без зависимостей

+
+
+ + \ No newline at end of file diff --git a/tests/test_web.py b/tests/test_web.py new file mode 100644 index 0000000..d711d8a --- /dev/null +++ b/tests/test_web.py @@ -0,0 +1,9 @@ +from fastapi.testclient import TestClient +from app.main import app + +client = TestClient(app) + +def test_home_page(): + response = client.get("/") + assert response.status_code == 200 + assert "FastAPI + SQLite" in response.text