diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml new file mode 100644 index 0000000..378a3be --- /dev/null +++ b/.github/workflows/backend-tests.yml @@ -0,0 +1,26 @@ +name: Backend Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + backend-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install dependencies + run: | + cd backend + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Run tests + run: | + cd backend + pytest -q diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..5281d4a --- /dev/null +++ b/backend/README.md @@ -0,0 +1,156 @@ +# Backend (FastAPI) + +This directory contains the backend application built with FastAPI. + +## Overview + +- **Framework**: FastAPI +- **Language**: Python (version 3.12 recommended) +- **Database**: PostgreSQL (version 17 recommended) +- **Authentication**: JWT (JSON Web Tokens) + +## Project Structure + +``` +backend/ +├── app/ +│ ├── config/ # Configuration settings (from .env) +│ ├── db/ # Database models and sessions +│ ├── logs/ # Log files +│ ├── routes/ # API routes and endpoints +│ ├── schemas/ # Pydantic schemas (data validation) +│ ├── services/ # Business logic services +│ ├── utils/ # Utility functions and constants +│ └── main.py # FastAPI application instance and main router +├── alembic/ # Alembic migrations (if using Alembic) +├── tests/ # Unit and integration tests +├── .env.example # Example environment variables +├── .gitignore # Git ignore file +├── alembic.ini # Alembic configuration (if used) +├── Dockerfile # Dockerfile for containerization +├── pyproject.toml # pyproject.toml for Poetry +├── README.md # This file +└── requirements.txt # Project dependencies +``` + +## Getting Started + +### Prerequisites + +- Python (version 3.12 recommended) +- Pip (Python package installer) +- A running PostgreSQL instance (version 17 recommended) + +### Installation & Setup + +1. **Navigate to the `backend` directory:** + ```bash + cd backend + ``` + +2. **Create and activate a virtual environment:** + ```bash + python -m venv venv + source venv/bin/activate # On Windows: venv\Scripts\activate + ``` + +3. **Install dependencies:** + ```bash + pip install -r requirements.txt + ``` + +4. **Environment Variables:** + Create a `.env` file in the `backend` directory by copying `.env.example` (if one exists) or by creating it manually. This file will store sensitive configuration and should not be committed to version control if it contains real secrets. + + Key environment variables: + + - `DATABASE_URL`: The connection string for your PostgreSQL database (e.g., `postgresql://user:password@host:port/dbname`). + - `SECRET_KEY`: A strong, unique secret key used for signing JWTs and other security purposes. Generate one using `openssl rand -hex 32`. + - `ALGORITHM`: The algorithm used for JWTs (e.g., `HS256`). + - `ACCESS_TOKEN_EXPIRE_MINUTES`: Expiration time for access tokens. + - `CORS_ORIGINS`: Comma-separated list of allowed CORS origins (e.g., `http://localhost:5173,https://yourdomain.com`). + + Example `.env`: + ```env + DATABASE_URL="postgresql://postgres:changethis@localhost:5432/appdb" + SECRET_KEY="your_very_strong_secret_key_here" + ALGORITHM="HS256" + ACCESS_TOKEN_EXPIRE_MINUTES=30 + CORS_ORIGINS="http://localhost:5173,http://127.0.0.1:5173" + ``` + +5. **Database Migrations (with Alembic):** + This project uses Alembic to manage database schema migrations. Ensure your `alembic.ini` is configured and your `migrations/env.py` correctly points to your SQLAlchemy models' metadata. + + Common commands (run from the `backend` directory): + + - **Generate a new migration script (after model changes):** + ```bash + alembic revision -m "your_descriptive_migration_message" --autogenerate + ``` + *(Always review autogenerated scripts carefully.)* + - **Apply all pending migrations to the database:** + ```bash + alembic upgrade head + ``` + - **View migration history:** + ```bash + alembic history + ``` + + For a comprehensive guide on using Alembic, including setup, writing migrations, and best practices, please refer to the [Database Migrations with Alembic](../../docs/backend/alembic-migrations.md) documentation. + +### Running the Development Server + +## Key FastAPI Concepts + +This project leverages several powerful features of FastAPI: + +* **Pydantic Models:** Used for data validation, serialization, and settings management (see `app/schemas/`). +* **APIRouter:** For structuring your application into multiple, manageable modules (see `app/api/v1/endpoints/` and `app/api/api.py`). +* **Dependency Injection:** Extensively used for database sessions, authentication, and other shared logic (see `app/api/v1/deps.py`). +* **Automatic API Docs:** Interactive Swagger UI available at `/docs` and ReDoc at `/redoc` when the development server is running. + +For a detailed guide on FastAPI usage within this project, including creating endpoints, working with Pydantic, and authentication, refer to the [FastAPI Guide for Backend Development](../../docs/backend/fastapi-guide.md). + + +Once dependencies are installed and the `.env` file is configured, you can run the FastAPI development server: + +```bash +uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 +``` + +- `--reload`: Enables auto-reloading when code changes. +- The API will be accessible at `http://localhost:8000`. +- Interactive API documentation (Swagger UI) will be at `http://localhost:8000/docs`. +- Alternative API documentation (ReDoc) will be at `http://localhost:8000/redoc`. + +## API Structure + +- API endpoints are defined in `app/api/v1/endpoints/`. +- Pydantic schemas for request/response validation are in `app/schemas/`. +- Database models (SQLAlchemy) are in `app/db/models/`. +- Business logic and CRUD operations are typically in `app/crud/` or `app/services/`. + +## Testing + +*(Describe how to run tests, e.g., `pytest`)* + +```bash +# Example: pytest +pytest +``` + +## Further Information + +**Project-Specific Guides:** + +- [FastAPI Guide for Backend Development](../../docs/backend/fastapi-guide.md) +- [Database Migrations with Alembic](../../docs/backend/alembic-migrations.md) + +**Official Documentation:** + +- [FastAPI](https://fastapi.tiangolo.com/) +- [Pydantic](https://docs.pydantic.dev/) +- [SQLAlchemy](https://www.sqlalchemy.org/) +- [Alembic](https://alembic.sqlalchemy.org/) diff --git a/backend/app/config/config.py b/backend/app/config/config.py index 994c787..89e60c5 100644 --- a/backend/app/config/config.py +++ b/backend/app/config/config.py @@ -12,7 +12,9 @@ class Settings(BaseSettings): APP_VERSION: str = "1.0.0" APP_NAME: str = "FastAPI React Starter" APP_DESCRIPTION: str = "FastAPI React Starter Template" - DATABASE_URL: str = "sqlite+aiosqlite:///./app.db" + ENVIRONMENT: str = "development" + DATABASE_URL: str = "" + TEST_DATABASE_URL: Optional[str] = "sqlite+aiosqlite:///./test_app.db" CORS_ORIGINS: List[str] = ["http://localhost:5173", "http://localhost:3000"] API_PREFIX: str = "/api" @@ -61,5 +63,8 @@ def get_settings() -> Settings: }, } -ENVIRONMENT = os.getenv("ENVIRONMENT", "development") +ENVIRONMENT = os.getenv("ENVIRONMENT", "development").lower() +# Ensure environment is one of the defined keys, default to development if not +if ENVIRONMENT not in LOGGING_CONFIG: + ENVIRONMENT = "development" CURRENT_LOGGING_CONFIG = LOGGING_CONFIG[ENVIRONMENT] diff --git a/backend/app/db/database.py b/backend/app/db/database.py index b2abc62..f02c737 100644 --- a/backend/app/db/database.py +++ b/backend/app/db/database.py @@ -4,7 +4,6 @@ from app.utils.logger import setup_logger from app.config import get_settings from urllib.parse import quote_plus -from typing import Optional settings = get_settings() logger = setup_logger(__name__) @@ -13,15 +12,18 @@ def get_database_url() -> str: """ Constructs the database URL based on configuration. + Prioritizes TEST_DATABASE_URL if set in 'testing' environment. Returns PostgreSQL URL if credentials are provided, otherwise falls back to SQLite. """ + if settings.ENVIRONMENT == "testing" and settings.TEST_DATABASE_URL: + return settings.TEST_DATABASE_URL + if settings.DB_NAME: # PostgreSQL connection password = quote_plus(settings.DB_PASSWORD) if settings.DB_PASSWORD else "" return f"postgresql+asyncpg://{settings.DB_USER}:{password}@{settings.DB_HOST}:{settings.DB_PORT}/{settings.DB_NAME}" - # SQLite connection (fallback) - return "sqlite+aiosqlite:///./app.db" + raise ValueError("No database URL found") def create_engine_with_retry(database_url: str): diff --git a/backend/app/schemas/auth.py b/backend/app/schemas/auth.py index dab468b..cf50930 100644 --- a/backend/app/schemas/auth.py +++ b/backend/app/schemas/auth.py @@ -31,6 +31,7 @@ class UserResponse(BaseModel): email_verified: bool role: str created_at: datetime + updated_at: datetime class Config: from_attributes = True diff --git a/backend/pytest.ini b/backend/pytest.ini new file mode 100644 index 0000000..5d6f8cf --- /dev/null +++ b/backend/pytest.ini @@ -0,0 +1,9 @@ +[pytest] +python_files = test_*.py +python_classes = Test* +filterwarnings = ignore::DeprecationWarning +python_functions = test_* +asyncio_mode = auto +markers = + integration: Marks tests as integration tests + unit: Marks tests as unit tests diff --git a/backend/requirements.txt b/backend/requirements.txt index d1ae038..4d969d5 100644 Binary files a/backend/requirements.txt and b/backend/requirements.txt differ diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py new file mode 100644 index 0000000..983274b --- /dev/null +++ b/backend/tests/conftest.py @@ -0,0 +1,104 @@ +"""Pytest fixtures for FastAPI backend tests.""" + +from __future__ import annotations + +import os +import sys +from typing import AsyncGenerator, Generator + +import pytest +import pytest_asyncio +from httpx import AsyncClient +from sqlalchemy.ext.asyncio import ( + AsyncSession, + async_sessionmaker, + create_async_engine, +) +from sqlalchemy.pool import NullPool +from sqlalchemy_utils import create_database, database_exists, drop_database + +# ---------------------------------------------------------------------------- +# Ensure test settings are loaded and project root is importable +# ---------------------------------------------------------------------------- +os.environ.setdefault("ENVIRONMENT", "testing") +PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +sys.path.insert(0, PROJECT_ROOT) + +from app.config import Settings, get_settings # noqa: E402 pylint: disable=wrong-import-position +from app.db import Base # noqa: E402 pylint: disable=wrong-import-position +from app.db.database import get_db # noqa: E402 pylint: disable=wrong-import-position +from app.main import app # noqa: E402 pylint: disable=wrong-import-position + +# --------------------------------------------------------------------------- +# Session-scoped helpers +# --------------------------------------------------------------------------- + + +@pytest.fixture(scope="session") +def settings_instance() -> Settings: + """Return fresh Settings for the test session.""" + get_settings.cache_clear() + return get_settings() + + +@pytest.fixture(scope="session") +def effective_test_db_url(settings_instance: Settings) -> str: + """Return DB URL for tests (env > default).""" + url = (settings_instance.TEST_DATABASE_URL or "").strip() + return url if url and url.lower() != "none" else settings_instance.DATABASE_URL + + +# --------------------------------------------------------------------------- +# Automatic DB lifecycle for the whole test session +# --------------------------------------------------------------------------- + + +@pytest_asyncio.fixture(scope="session", autouse=True) +async def _session_db_lifecycle(effective_test_db_url: str): + is_sqlite = effective_test_db_url.startswith("sqlite") + + created_db = False + sync_url = None + if not is_sqlite: + sync_url = effective_test_db_url.replace("postgresql+asyncpg", "postgresql") + if not database_exists(sync_url): + create_database(sync_url) + created_db = True + + engine = create_async_engine(effective_test_db_url, poolclass=NullPool) + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + + yield # tests run here + + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.drop_all) + await engine.dispose() + + if created_db and sync_url: + drop_database(sync_url) + + +# --------------------------------------------------------------------------- +# Per-test fixtures +# --------------------------------------------------------------------------- + + +@pytest_asyncio.fixture() +async def db_session(effective_test_db_url: str) -> AsyncGenerator[AsyncSession, None]: + engine = create_async_engine(effective_test_db_url, poolclass=NullPool) + session_factory = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) + async with session_factory() as session: + yield session + await engine.dispose() + + +@pytest_asyncio.fixture() +async def test_client(db_session: AsyncSession) -> AsyncGenerator[AsyncClient, None]: + def _override_get_db() -> Generator[AsyncSession, None, None]: + yield db_session + + app.dependency_overrides[get_db] = _override_get_db + async with AsyncClient(app=app, base_url="http://test") as client: + yield client + app.dependency_overrides.pop(get_db, None) diff --git a/backend/tests/integration/test_auth_api.py b/backend/tests/integration/test_auth_api.py new file mode 100644 index 0000000..3f2ff00 --- /dev/null +++ b/backend/tests/integration/test_auth_api.py @@ -0,0 +1,132 @@ +import pytest +from httpx import AsyncClient +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select + +# UserCreate schema is not directly used, json payloads are dicts +from app.db.models import User # To verify DB state +from app.config import get_settings + +settings = get_settings() +API_PREFIX = settings.API_PREFIX + + +@pytest.mark.asyncio +async def test_register_new_user_success(test_client: AsyncClient, db_session: AsyncSession): + """Test successful user registration.""" + user_data = { + "email": "testuser@example.com", + "username": "testuser", + "password": "strongpassword123", + } + response = await test_client.post(f"{API_PREFIX}/auth/register", json=user_data) + + assert response.status_code == 200 # Or 201 if you prefer for creation + response_data = response.json() + assert response_data["email"] == user_data["email"] + assert response_data["username"] == user_data["username"] + assert "id" in response_data + assert "created_at" in response_data + assert "updated_at" in response_data + + # Verify user in database + stmt = select(User).where(User.email == user_data["email"]) + result = await db_session.execute(stmt) + db_user = result.scalar_one_or_none() + assert db_user is not None + assert db_user.username == user_data["username"] + + +@pytest.mark.asyncio +async def test_register_user_email_exists(test_client: AsyncClient, db_session: AsyncSession): + """Test registration fails if email already exists.""" + # First, create a user + existing_user_data = { + "email": "existing@example.com", + "username": "existinguser", + "password": "password123", + } + await test_client.post(f"{API_PREFIX}/auth/register", json=existing_user_data) + + # Attempt to register with the same email + new_user_data = { + "email": "existing@example.com", + "username": "newuser", + "password": "newpassword456", + } + response = await test_client.post(f"{API_PREFIX}/auth/register", json=new_user_data) + + assert response.status_code == 400 + assert response.json()["detail"] == "Email already registered" + + +@pytest.mark.asyncio +async def test_register_user_username_exists(test_client: AsyncClient, db_session: AsyncSession): + """Test registration fails if username already exists.""" + # First, create a user + existing_user_data = { + "email": "another@example.com", + "username": "existingusername", + "password": "password123", + } + await test_client.post(f"{API_PREFIX}/auth/register", json=existing_user_data) + + # Attempt to register with the same username + new_user_data = { + "email": "newemail@example.com", + "username": "existingusername", + "password": "newpassword456", + } + response = await test_client.post(f"{API_PREFIX}/auth/register", json=new_user_data) + + assert response.status_code == 400 + assert response.json()["detail"] == "Username already taken" + + +@pytest.mark.asyncio +async def test_login_success(test_client: AsyncClient, db_session: AsyncSession): + """Test successful user login.""" + user_data = { + "email": "loginuser@example.com", + "username": "loginuser", + "password": "strongpassword123", + } + # Register user first + await test_client.post(f"{API_PREFIX}/auth/register", json=user_data) + + login_payload = {"email": user_data["email"], "password": user_data["password"]} + response = await test_client.post(f"{API_PREFIX}/auth/login", json=login_payload) + + assert response.status_code == 200 + response_data = response.json() + assert "access_token" in response_data + assert response_data["token_type"] == "bearer" + + +@pytest.mark.asyncio +async def test_login_incorrect_password(test_client: AsyncClient, db_session: AsyncSession): + """Test login fails with incorrect password.""" + user_data = { + "email": "loginfail@example.com", + "username": "loginfailuser", + "password": "correctpassword", + } + await test_client.post(f"{API_PREFIX}/auth/register", json=user_data) + + login_payload = {"email": user_data["email"], "password": "wrongpassword"} + response = await test_client.post(f"{API_PREFIX}/auth/login", json=login_payload) + + assert response.status_code == 401 + assert response.json()["detail"] == "Incorrect email or password" + + +@pytest.mark.asyncio +async def test_login_user_not_found(test_client: AsyncClient): + """Test login fails if user does not exist.""" + login_payload = {"email": "nonexistent@example.com", "password": "anypassword"} + response = await test_client.post(f"{API_PREFIX}/auth/login", json=login_payload) + + assert ( + response.status_code == 401 + ) # Or 404, depending on desired behavior for non-existent user + assert response.json()["detail"] == "Incorrect email or password" diff --git a/backend/tests/unit/test_auth_service.py b/backend/tests/unit/test_auth_service.py new file mode 100644 index 0000000..752a725 --- /dev/null +++ b/backend/tests/unit/test_auth_service.py @@ -0,0 +1,58 @@ +import pytest +from datetime import datetime, timedelta +from jose import jwt, JWTError +from app.services.auth import create_access_token +from app.config import get_settings + +settings = get_settings() + + +def test_create_access_token_success(): + """Test that create_access_token generates a valid JWT.""" + test_subject = "testuser@example.com" + data = {"sub": test_subject} + + token = create_access_token(data) + + assert isinstance(token, str) + + try: + payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) + assert payload.get("sub") == test_subject + assert "exp" in payload + # Check if expiry is roughly correct (within a small delta) + expected_expiry = datetime.utcnow() + timedelta( + minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES + ) + actual_expiry = datetime.utcfromtimestamp(payload["exp"]) + assert abs((expected_expiry - actual_expiry).total_seconds()) < 5 # 5 seconds tolerance + except JWTError as e: + pytest.fail(f"JWT decoding failed: {e}") + + +def test_create_access_token_different_data(): + """Test token creation with different subject and additional data.""" + test_subject = "anotheruser@example.com" + additional_data = {"user_id": 123, "role": "admin"} + data = {"sub": test_subject, **additional_data} + + token = create_access_token(data) + assert isinstance(token, str) + + try: + payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) + assert payload.get("sub") == test_subject + assert payload.get("user_id") == 123 + assert payload.get("role") == "admin" + assert "exp" in payload + except JWTError: + pytest.fail("JWT decoding failed for token with additional data.") + + +# Example of how you might test for expected failures if the function had validation +# (currently, create_access_token doesn't have input validation that would cause it to fail before encoding) +# def test_create_access_token_missing_sub(): +# """Test token creation fails if 'sub' is missing (if validation was added).""" +# data = {"foo": "bar"} # Missing 'sub' +# with pytest.raises(ValueError): # Or whatever appropriate error +# create_access_token(data) diff --git a/deployments/README.md b/deployments/README.md new file mode 100644 index 0000000..7c66d6a --- /dev/null +++ b/deployments/README.md @@ -0,0 +1,6 @@ +# Deployment Configurations + +This directory contains example deployment configurations for various cloud providers. + +- `google-cloud/`: Contains configurations for deploying to Google Cloud Platform. +- `aws/`: Contains configurations for deploying to Amazon Web Services. diff --git a/deployments/aws/README.md b/deployments/aws/README.md new file mode 100644 index 0000000..03fa9f5 --- /dev/null +++ b/deployments/aws/README.md @@ -0,0 +1,19 @@ +# AWS Deployment Configuration + +This directory contains example configurations for deploying the application to AWS using ECS with Fargate. + +### Files: + +- `buildspec.yml`: This file is used by AWS CodeBuild to build the Docker images and push them to Amazon ECR (Elastic Container Registry). + +- `task-definitions/`: This directory contains the ECS task definition templates. + - `backend-task-def.json`: Task definition for the backend service. + - `frontend-task-def.json`: Task definition for the frontend service. + +### Deployment Steps: + +1. **Create ECR Repositories**: Create two ECR repositories, one for the backend and one for the frontend. +2. **Set up CodeBuild**: Create a CodeBuild project linked to your source code repository. Use the `buildspec.yml` in this directory as the build specification. +3. **Create ECS Cluster**: Create an ECS cluster to run your services. +4. **Create Task Definitions**: Register new task definitions in ECS using the JSON files in the `task-definitions` directory. You will need to replace the placeholder values (like `YOUR_ECR_REPO_URI`) with your actual ECR repository URIs. +5. **Create ECS Services**: Create two services in your ECS cluster, one for the frontend and one for the backend, using the task definitions you just created. Configure them with a load balancer to expose them to the internet. diff --git a/deployments/aws/buildspec.yml b/deployments/aws/buildspec.yml new file mode 100644 index 0000000..c5ef43a --- /dev/null +++ b/deployments/aws/buildspec.yml @@ -0,0 +1,32 @@ +version: 0.2 + +phases: + pre_build: + commands: + - echo Logging in to Amazon ECR... + - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com + - REPOSITORY_URI_BACKEND=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$BACKEND_IMAGE_REPO_NAME + - REPOSITORY_URI_FRONTEND=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$FRONTEND_IMAGE_REPO_NAME + - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7) + - IMAGE_TAG=${COMMIT_HASH:=latest} + build: + commands: + - echo Build started on `date` + - echo Building the backend Docker image... + - docker build -t $REPOSITORY_URI_BACKEND:latest ./backend + - docker tag $REPOSITORY_URI_BACKEND:latest $REPOSITORY_URI_BACKEND:$IMAGE_TAG + - echo Building the frontend Docker image... + - docker build -t $REPOSITORY_URI_FRONTEND:latest ./frontend + - docker tag $REPOSITORY_URI_FRONTEND:latest $REPOSITORY_URI_FRONTEND:$IMAGE_TAG + post_build: + commands: + - echo Build completed on `date` + - echo Pushing the Docker images... + - docker push $REPOSITORY_URI_BACKEND:latest + - docker push $REPOSITORY_URI_BACKEND:$IMAGE_TAG + - docker push $REPOSITORY_URI_FRONTEND:latest + - docker push $REPOSITORY_URI_FRONTEND:$IMAGE_TAG + - echo Writing image definitions file... + - printf '[{"name":"%s","imageUri":"%s"},{"name":"%s","imageUri":"%s"}]' $BACKEND_CONTAINER_NAME $REPOSITORY_URI_BACKEND:$IMAGE_TAG $FRONTEND_CONTAINER_NAME $REPOSITORY_URI_FRONTEND:$IMAGE_TAG > imagedefinitions.json +artifacts: + files: imagedefinitions.json diff --git a/deployments/aws/task-definitions/backend-task-def.json b/deployments/aws/task-definitions/backend-task-def.json new file mode 100644 index 0000000..8e672dc --- /dev/null +++ b/deployments/aws/task-definitions/backend-task-def.json @@ -0,0 +1,32 @@ +{ + "family": "backend-service", + "containerDefinitions": [ + { + "name": "backend-container", + "image": "", + "cpu": 256, + "memory": 512, + "portMappings": [ + { + "containerPort": 8000, + "hostPort": 8000, + "protocol": "tcp" + } + ], + "essential": true, + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "/ecs/backend-service", + "awslogs-region": "", + "awslogs-stream-prefix": "ecs" + } + } + } + ], + "requiresCompatibilities": ["FARGATE"], + "networkMode": "awsvpc", + "cpu": "256", + "memory": "512", + "executionRoleArn": "arn:aws:iam:::role/ecsTaskExecutionRole" +} diff --git a/deployments/aws/task-definitions/frontend-task-def.json b/deployments/aws/task-definitions/frontend-task-def.json new file mode 100644 index 0000000..6d94a63 --- /dev/null +++ b/deployments/aws/task-definitions/frontend-task-def.json @@ -0,0 +1,32 @@ +{ + "family": "frontend-service", + "containerDefinitions": [ + { + "name": "frontend-container", + "image": "", + "cpu": 256, + "memory": 512, + "portMappings": [ + { + "containerPort": 80, + "hostPort": 80, + "protocol": "tcp" + } + ], + "essential": true, + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "/ecs/frontend-service", + "awslogs-region": "", + "awslogs-stream-prefix": "ecs" + } + } + } + ], + "requiresCompatibilities": ["FARGATE"], + "networkMode": "awsvpc", + "cpu": "256", + "memory": "512", + "executionRoleArn": "arn:aws:iam:::role/ecsTaskExecutionRole" +} diff --git a/deployments/google-cloud/cloudbuild.yaml b/deployments/google-cloud/cloudbuild.yaml new file mode 100644 index 0000000..a67e80e --- /dev/null +++ b/deployments/google-cloud/cloudbuild.yaml @@ -0,0 +1,72 @@ +# Google Cloud Build configuration for deploying to Cloud Run + +steps: + # 1. Build and push the backend Docker image + - name: 'gcr.io/cloud-builders/docker' + args: + - 'build' + - '-t' + - '$_GCR_HOSTNAME/$PROJECT_ID/$_BACKEND_SERVICE_NAME:$COMMIT_SHA' + - './backend' + id: 'Build Backend' + + - name: 'gcr.io/cloud-builders/docker' + args: ['push', '$_GCR_HOSTNAME/$PROJECT_ID/$_BACKEND_SERVICE_NAME:$COMMIT_SHA'] + id: 'Push Backend' + + # 2. Deploy the backend to Cloud Run + - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk' + entrypoint: gcloud + args: + - 'run' + - 'deploy' + - '$_BACKEND_SERVICE_NAME' + - '--image=$_GCR_HOSTNAME/$PROJECT_ID/$_BACKEND_SERVICE_NAME:$COMMIT_SHA' + - '--region' + - '$_REGION' + - '--platform' + - 'managed' + - '--allow-unauthenticated' + id: 'Deploy Backend' + + # 3. Build and push the frontend Docker image + - name: 'gcr.io/cloud-builders/docker' + args: + - 'build' + - '-t' + - '$_GCR_HOSTNAME/$PROJECT_ID/$_FRONTEND_SERVICE_NAME:$COMMIT_SHA' + - './frontend' + id: 'Build Frontend' + + - name: 'gcr.io/cloud-builders/docker' + args: ['push', '$_GCR_HOSTNAME/$PROJECT_ID/$_FRONTEND_SERVICE_NAME:$COMMIT_SHA'] + id: 'Push Frontend' + + # 4. Deploy the frontend to Cloud Run + - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk' + entrypoint: gcloud + args: + - 'run' + - 'deploy' + - '$_FRONTEND_SERVICE_NAME' + - '--image=$_GCR_HOSTNAME/$PROJECT_ID/$_FRONTEND_SERVICE_NAME:$COMMIT_SHA' + - '--region' + - '$_REGION' + - '--platform' + - 'managed' + - '--allow-unauthenticated' + id: 'Deploy Frontend' + +# Substitutions - these can be set in your Cloud Build trigger +substitutions: + _GCR_HOSTNAME: us-central1-docker.pkg.dev + _REGION: us-central1 + _BACKEND_SERVICE_NAME: api-service + _FRONTEND_SERVICE_NAME: frontend-service + +images: + - '$_GCR_HOSTNAME/$PROJECT_ID/$_BACKEND_SERVICE_NAME:$COMMIT_SHA' + - '$_GCR_HOSTNAME/$PROJECT_ID/$_FRONTEND_SERVICE_NAME:$COMMIT_SHA' + +options: + logging: CLOUD_LOGGING_ONLY diff --git a/docs/backend/alembic-migrations.md b/docs/backend/alembic-migrations.md new file mode 100644 index 0000000..a9122a6 --- /dev/null +++ b/docs/backend/alembic-migrations.md @@ -0,0 +1,146 @@ +# Database Migrations with Alembic + +Alembic is a lightweight database migration tool for SQLAlchemy. It allows you to manage changes to your database schema over time in a structured and version-controlled way. + +## Why Use Alembic? + +* **Version Control for Your Database:** Track schema changes alongside your application code. +* **Repeatable Deployments:** Ensure consistent database schemas across different environments (development, staging, production). +* **Collaboration:** Makes it easier for multiple developers to work on database schema changes without conflicts. +* **Automated Schema Changes:** Avoids manual SQL scripting for schema modifications, reducing errors. + +## Core Concepts + +* **Migration Environment:** A directory (usually named `alembic` or `alembic` in your backend root) containing Alembic's configuration and migration scripts. +* **`alembic.ini`:** The main configuration file for Alembic. It specifies database connection details, script locations, and other settings. +* **`env.py`:** A Python script within the migration environment that Alembic executes when running commands. It's where you configure how Alembic connects to your database and how it discovers your SQLAlchemy models for autogeneration. + * Crucially, `env.py` needs to know about your SQLAlchemy models' metadata (`target_metadata = Base.metadata`). +* **Revision:** A single migration script representing a set of changes to the database schema. Each revision has a unique ID. +* **`upgrade()` function:** Contains the operations to apply the schema changes (e.g., create a table, add a column). +* **`downgrade()` function:** Contains the operations to revert the schema changes. + +## Setting Up Alembic (If not already set up) + +If Alembic is not yet part of the project, you would typically initialize it: + +1. Install Alembic: `pip install alembic` +2. Initialize the environment (from your `backend` directory): + ```bash + alembic init alembic + ``` + (This creates a `alembic` directory and an `alembic.ini` file.) +3. Configure `alembic.ini` with your database URL (`sqlalchemy.url`). +4. Configure `alembic/env.py`: + * Ensure `target_metadata` points to your SQLAlchemy `Base.metadata` from `app.db.base` or wherever your models are defined. + * Ensure the Python path is set up so `env.py` can import your application modules. + +## Common Alembic Commands + +Run these commands from the directory containing `alembic.ini` (usually your `backend` root). + +### 1. Generating a New Migration Script + +When you make changes to your SQLAlchemy models (e.g., add a new model, add a column to an existing model), you need to generate a migration script. + +**Manual Revision (Recommended for understanding):** +```bash +alembic revision -m "create_users_table" +``` +This creates a new, empty migration file in `alembic/versions/`. You then manually edit this file to define the `upgrade()` and `downgrade()` functions using Alembic's `op` object. + +**Example (Manual - creating a users table):** +```python +# In the generated migration_xyz.py file +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = 'your_revision_id' +down_revision = 'previous_revision_id_or_None' +branch_labels = None +depends_on = None + +def upgrade(): + op.create_table( + 'users', + sa.Column('id', sa.Integer, primary_key=True, index=True), + sa.Column('email', sa.String, unique=True, index=True, nullable=False), + sa.Column('hashed_password', sa.String, nullable=False), + sa.Column('is_active', sa.Boolean, default=True) + ) + +def downgrade(): + op.drop_table('users') +``` + +**Autogenerate Revision (Use with caution and always review):** +Alembic can compare your SQLAlchemy models against the current database state and attempt to generate the migration script automatically. + +```bash +alembic revision -m "add_bio_to_users" --autogenerate +``` +* **Important:** Always review autogenerated scripts carefully. Alembic might not detect all changes perfectly (e.g., complex constraints, server defaults, changes to types that are not straightforward). You might need to manually adjust the script. + +### 2. Applying Migrations + +To apply pending migrations to your database: + +* **Apply all pending migrations (upgrade to the latest revision):** + ```bash + alembic upgrade head + ``` +* **Apply migrations up to a specific revision:** + ```bash + alembic upgrade + ``` +* **Apply a single next migration:** + ```bash + alembic upgrade +1 + ``` + +### 3. Reverting Migrations (Downgrading) + +To undo migrations: + +* **Revert the last applied migration:** + ```bash + alembic downgrade -1 + ``` +* **Revert migrations down to a specific revision:** + ```bash + alembic downgrade + ``` +* **Revert all migrations (to an empty database state, if applicable):** + ```bash + alembic downgrade base + ``` + **(Use with extreme caution on production data!)** + +### 4. Viewing Migration Status + +* **Show current revision(s) and migration history:** + ```bash + alembic history + ``` +* **Show the current revision ID(s) the database is at:** + ```bash + alembic current + ``` +* **Show all revisions, indicating which are applied:** + ```bash + alembic history --verbose + ``` + +## Best Practices + +* **Keep Migrations Small and Focused:** Each migration should represent a single, logical change. +* **Test Migrations:** Always test your migrations in a development or staging environment before applying them to production. +* **Never Edit an Applied Migration:** Once a migration has been applied to a shared database (like production), do not edit its script. Instead, create a new migration to correct or alter it. +* **Backup Your Database:** Before running significant migrations on production, always back up your database. +* **Review Autogenerated Scripts:** Thoroughly inspect any script generated with `--autogenerate`. +* **Consider Data Migrations Separately:** For changes that involve transforming data (not just schema), you might write custom Python code within your migration script or handle it as a separate process. Alembic's `op.bulk_insert` and `op.execute` can be useful here. + +## Further Reading + +* [Alembic Official Tutorial](https://alembic.sqlalchemy.org/en/latest/tutorial.html) +* [Alembic Operation Reference](https://alembic.sqlalchemy.org/en/latest/ops.html) diff --git a/docs/backend/fastapi-guide.md b/docs/backend/fastapi-guide.md new file mode 100644 index 0000000..e25164c --- /dev/null +++ b/docs/backend/fastapi-guide.md @@ -0,0 +1,140 @@ +# FastAPI Guide for Backend Development + +This guide provides an overview of how FastAPI is used in this project and key concepts for backend development. + +## Why FastAPI? + +FastAPI is a modern, high-performance web framework for building APIs with Python 3.7+ based on standard Python type hints. Key benefits include: + +* **High Performance:** On par with NodeJS and Go, thanks to Starlette and Pydantic. +* **Fast to Code:** Increase the speed to develop features by about 200% to 300%. +* **Fewer Bugs:** Reduce about 40% of human-induced errors (thanks to type hints). +* **Intuitive:** Great editor support. Completion everywhere. Less time debugging. +* **Easy:** Designed to be easy to use and learn. Less time reading docs. +* **Short:** Minimize code duplication. Multiple features from each parameter declaration. +* **Robust:** Get production-ready code. With automatic interactive documentation. +* **Standards-based:** Based on (and fully compatible with) the open standards for APIs: OpenAPI and JSON Schema. + +## Backend Project Structure + +Our backend is organized to promote modularity and maintainability. A typical structure (located in `backend/app/`) might look like this: + +``` +backend/ +├── app/ +│ ├── config/ # Configuration settings (from .env) +│ ├── db/ # Database models and sessions +│ ├── logs/ # Log files +│ ├── routes/ # API routes and endpoints +│ ├── schemas/ # Pydantic schemas (data validation) +│ ├── services/ # Business logic services +│ ├── utils/ # Utility functions and constants +│ └── main.py # FastAPI application instance and main router +├── alembic/ # Alembic migrations (if using Alembic) +├── tests/ # Unit and integration tests +├── .env.example # Example environment variables +├── .gitignore +├── alembic.ini # Alembic configuration (if used) +├── Dockerfile # Dockerfile for containerization +├── pyproject.toml # pyproject.toml for Poetry +├── README.md # This file +└── requirements.txt # Project dependencies +``` + +## Defining Endpoints + +Endpoints are defined using path operation decorators (`@router.get`, `@router.post`, etc.) on functions within modules in `app/api/v1/endpoints/`. These routers are then included in the main FastAPI app instance in `app/main.py`. + +**Example (`app/api/v1/endpoints/items.py`):** +```python +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session +from app import crud, schemas +from app.api.v1 import deps # Assuming deps.py for get_db + +router = APIRouter() + +@router.post("/items/", response_model=schemas.Item) +def create_item(item: schemas.ItemCreate, db: Session = Depends(deps.get_db)): + return crud.item.create(db=db, item=item) + +@router.get("/items/{item_id}", response_model=schemas.Item) +def read_item(item_id: int, db: Session = Depends(deps.get_db)): + db_item = crud.item.get(db, id=item_id) + if db_item is None: + raise HTTPException(status_code=404, detail="Item not found") + return db_item +``` + +## Data Validation with Pydantic + +Pydantic is used extensively for data validation, serialization, and settings management. + +* **Request Bodies:** Define a Pydantic model (schema) that inherits from `BaseModel`. FastAPI will automatically validate incoming request data against this schema. +* **Response Models:** Use the `response_model` parameter in path operation decorators to define the schema for the response. FastAPI will filter and serialize the output data to match this schema. +* **Path and Query Parameters:** Type hints for path and query parameters are also validated. + +**Example (`app/schemas/item.py`):** +```python +from pydantic import BaseModel + +class ItemBase(BaseModel): + title: str + description: str | None = None + +class ItemCreate(ItemBase): + pass + +class Item(ItemBase): + id: int + owner_id: int + + class Config: + orm_mode = True # or from_attributes = True for Pydantic v2 +``` + +## Dependency Injection + +FastAPI's dependency injection system is a powerful feature used for: + +* **Database Sessions:** Providing a database session to path operation functions (e.g., `db: Session = Depends(deps.get_db)`). +* **Authentication & Authorization:** Getting the current user or validating security scopes (e.g., `current_user: models.User = Depends(deps.get_current_active_user)`). +* **Shared Logic:** Reusing common logic or parameters. + +Dependencies are defined as functions (often in `app/api/v1/deps.py`) that FastAPI will call. + +## Asynchronous Operations (`async`/`await`) + +FastAPI supports asynchronous path operation functions using `async def`. This is crucial for I/O-bound operations (like database calls or external API requests) to prevent blocking the server. + +```python +@router.get("/async-items/") +async def read_async_items(): + # Example: await some_async_io_operation() + return [{"name": "Async Item 1"}, {"name": "Async Item 2"}] +``` +Ensure your database drivers and other I/O libraries support `async` operations (e.g., `asyncpg` for PostgreSQL with SQLAlchemy). + +## Authentication + +This project typically uses JWT (JSON Web Tokens) for authentication. The flow usually involves: +1. An endpoint (e.g., `/login/access-token`) where users submit credentials. +2. If valid, the server generates and returns an access token. +3. The client includes this token in the `Authorization` header (e.g., `Bearer `) for subsequent requests to protected endpoints. +4. A dependency (`get_current_user`) validates the token and retrieves the user. + +Security utilities for password hashing and JWT management are often in `app/core/security.py`. + +## Automatic API Documentation + +FastAPI automatically generates interactive API documentation based on your code, Pydantic models, and OpenAPI schema. + +* **Swagger UI:** Accessible at `/docs` (e.g., `http://localhost:8000/docs`). +* **ReDoc:** Accessible at `/redoc` (e.g., `http://localhost:8000/redoc`). + +This documentation is invaluable for frontend developers and API consumers. + +## Further Reading + +* [FastAPI Official Tutorial](https://fastapi.tiangolo.com/tutorial/) +* [Pydantic Documentation](https://docs.pydantic.dev/) diff --git a/docs/deployment.md b/docs/deployment.md index 7e0a567..b6b1659 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -188,4 +188,52 @@ This will generate the static site in the `docs/site/` directory (or as configur --- -Deploying a web application involves many considerations. This guide provides a starting point. Always adapt the configuration to your specific security and performance requirements. \ No newline at end of file +## 9. Cloud Provider Deployments + +While Docker Compose is excellent for single-server deployments, you might want to leverage managed cloud platforms like Google Cloud Run or AWS ECS for better scalability, reliability, and easier management. + +The `deployments/` directory in this project contains ready-to-use configuration files for these platforms. + +### 9.1. Google Cloud Run + +Google Cloud Run is a serverless platform that automatically scales your containerized applications. + +- **Configuration File:** `deployments/google-cloud/cloudbuild.yaml` +- **Method:** This file is used with Google Cloud Build to automatically build your backend and frontend Docker images, push them to Google Container Registry (GCR), and deploy them as two separate services on Cloud Run. + +**To Deploy:** +1. **Prerequisites:** + * A Google Cloud Project with billing enabled. + * The `gcloud` CLI installed and configured. + * Cloud Build, Cloud Run, and Container Registry APIs enabled. +2. **Configure Substitutions:** The `cloudbuild.yaml` uses substitution variables (e.g., `_REGION`, `_BACKEND_SERVICE_NAME`). You can set these directly in a Cloud Build trigger or when running the build manually. +3. **Set up a Trigger:** In the Google Cloud Console, navigate to Cloud Build and create a trigger that points to your source code repository. Configure it to use the `deployments/google-cloud/cloudbuild.yaml` file. +4. **Push to Deploy:** Pushing a commit to your configured branch will automatically trigger the build and deployment process. + +*For detailed steps, refer to the [Google Cloud Run documentation](https://cloud.google.com/run/docs).* + +### 9.2. AWS Elastic Container Service (ECS) with Fargate + +AWS ECS is a highly scalable container orchestration service. Fargate is a serverless compute engine for ECS, so you don't have to manage servers. + +- **Configuration Files:** + - `deployments/aws/buildspec.yml`: For AWS CodeBuild to build and push images to ECR. + - `deployments/aws/task-definitions/`: Contains JSON templates for the backend and frontend ECS task definitions. +- **Method:** + 1. **AWS CodePipeline:** Create a pipeline that uses your source repository (e.g., GitHub, AWS CodeCommit). + 2. **Build Stage:** Add a build stage that uses AWS CodeBuild with the `buildspec.yml` file. This will build your Docker images and push them to Amazon ECR. + 3. **Deploy Stage:** Add a deploy stage that uses the "Amazon ECS" action to deploy your services using the task definitions. + +**To Deploy:** +1. **Prerequisites:** + * An AWS account. + * The `aws` CLI installed and configured. + * ECR repositories for your backend and frontend images. + * An ECS cluster. +2. **Customize Task Definitions:** Update the placeholder values (e.g., ``, ``) in the `*.json` task definition files. +3. **Set up CodePipeline:** Follow the AWS documentation to create a pipeline that automates the build and deploy process from your repository to your ECS cluster. + +*For detailed steps, refer to the [AWS ECS User Guide](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html).* + + +Deploying a web application involves many considerations. This guide provides a starting point for self hosting and two of the top cloud providers. Always adapt the configuration to your specific security and performance requirements. You have a way to go before you can deploy your project from this template, once you have reached a point where you can deploy your project reach out to the maintainer for assistance. \ No newline at end of file diff --git a/docs/development.md b/docs/development.md index aa15431..0fce6fd 100644 --- a/docs/development.md +++ b/docs/development.md @@ -48,16 +48,20 @@ For a more detailed visual structure, refer to the `README.md` or the [Project O ### Running Tests -*(Documentation on the testing framework (e.g., Pytest) and how to run tests should be added here. This might include setup, fixtures, and example commands.)* - -Example (if using Pytest): +This backend is setup with pytest and coverage. To run tests, navigate to the backend directory and run: ```bash -# Navigate to backend directory cd backend pytest ``` +To run tests with coverage, navigate to the backend directory and run: + +```bash +cd backend +pytest --cov=app +``` + ### Database Migrations The project uses Alembic for database migrations, managed via `manage.py`. @@ -143,15 +147,7 @@ logger.error("This is an error message.") ### Running Tests -*(Documentation on the testing framework (e.g., Vitest, React Testing Library) and how to run tests should be added here. This might include setup, example tests, and commands.)* - -Example (if using Vitest with an `npm run test` script): - -```bash -# Navigate to frontend directory -cd frontend -npm run test -``` +*(Documentation on the testing framework (e.g., Vitest, React Testing Library) and how to run tests should be added here. This will be updated as tests for the frontend are added in the future.)* ### Styling diff --git a/docs/frontend/shadcn-ui.md b/docs/frontend/shadcn-ui.md new file mode 100644 index 0000000..b9928dd --- /dev/null +++ b/docs/frontend/shadcn-ui.md @@ -0,0 +1,69 @@ +# Shadcn UI Guide + +This project leverages [Shadcn UI](https://ui.shadcn.com/) for building its user interface. Shadcn UI is not a traditional component library but rather a collection of beautifully designed components that you can copy and paste into your application. This approach gives you full ownership and control over the code, allowing for deep customization. + +## Philosophy + +Understanding the philosophy behind Shadcn UI is key to using it effectively: + +* **You Own the Code:** When you add a component, its source code is added directly to your project (typically under `frontend/src/components/ui/`). You can modify it as much as you need. +* **Not a Dependency:** It's not a package you install from npm in the usual sense (apart from the CLI tool). This means no versioning conflicts with a third-party library and no waiting for the library maintainer to update or fix something. +* **Built with Radix UI and Tailwind CSS:** Components are generally built using [Radix UI Primitives](https://www.radix-ui.com/) for accessibility and behavior, and styled with [Tailwind CSS](https://tailwindcss.com/) for a utility-first approach. + +## Adding Components + +To add new components to your project, use the Shadcn UI CLI. Make sure you are in the `frontend` directory: + +```bash +npx shadcn-ui@latest add [component-name] +``` + +For example, to add an `alert-dialog`: + +```bash +npx shadcn-ui@latest add alert-dialog +``` + +This command will typically do the following: +1. Check your `components.json` for configuration. +2. Install any necessary dependencies for the component (e.g., `@radix-ui/react-alert-dialog`). +3. Add the component's source code to `frontend/src/components/ui/`. + +## Configuration (`components.json`) + +The behavior of the Shadcn UI CLI is configured by the `components.json` file located in your `frontend` directory. This file defines: + +* `$schema`: The schema URL for `components.json`. +* `style`: The style of components to use (e.g., "default", "new-york"). This template uses `default`. +* `rsc`: Whether to install React Server Components (RSC) compatible components (true/false). +* `tsx`: Whether to use TypeScript for components (true/false). This template uses `true`. +* `tailwind`: + * `config`: Path to your Tailwind CSS configuration file. + * `css`: Path to your main CSS file where Tailwind directives are imported. + * `baseColor`: The base color for your theme (e.g., "slate", "zinc"). + * `cssVariables`: Whether to use CSS variables for theming. +* `aliases`: + * `components`: Path alias for where your UI components are stored (e.g., `~/components`, which might map to `src/components`). + * `utils`: Path alias for utility functions (e.g., `~/lib/utils` for `cn()` function). + +Ensure this file is configured correctly for your project structure if you deviate from the template's defaults. + +## Customizing Components + +Since the component code is directly in your project (e.g., `frontend/src/components/ui/button.tsx`), you can customize it by: + +* **Modifying Styles:** Directly change Tailwind CSS classes within the component's TSX file. +* **Altering Behavior:** Change the underlying Radix UI primitives or add your own logic. +* **Extending Functionality:** Add new props or features to the component. + +## Theming + +Shadcn UI components are designed to be themed using CSS variables. Check the `frontend/src/index.css` (or your main CSS file) for the theme variables defined by Shadcn UI when you initialized it. You can customize these variables to change the overall look and feel of the components. + +## Best Practices + +* **Keep CLI Updated:** Occasionally run `npx shadcn-ui@latest init` to ensure your CLI and local setup are up-to-date with any changes from Shadcn UI (be careful and review changes if you have heavily customized `components.json`). +* **Refer to Official Docs:** For specific component APIs, props, and usage examples, always refer to the [official Shadcn UI documentation](https://ui.shadcn.com/docs/components/accordion). +* **Understand Dependencies:** When you add a component, it might install peer dependencies (like Radix UI packages). Be aware of these in your `package.json`. + +By following this guide and leveraging the official documentation, you can effectively use Shadcn UI to build a beautiful and highly customizable frontend for this application. diff --git a/docs/frontend/tailwind-v4.md b/docs/frontend/tailwind-v4.md new file mode 100644 index 0000000..14286ec --- /dev/null +++ b/docs/frontend/tailwind-v4.md @@ -0,0 +1,39 @@ +# Tailwind CSS v4 Integration + +This project utilizes **Tailwind CSS v4**, the latest iteration of the utility-first CSS framework. Version 4 brings significant improvements in performance, a new engine, and a more streamlined developer experience, designed for the modern web. + +## Key Features & Changes from v3 + +While a comprehensive list of changes can be found in the [official Tailwind CSS v4 documentation](https://tailwindcss.com/docs), here are some highlights relevant to users of this template: + +* **New Engine (Oxide):** Tailwind CSS v4 features a new, high-performance engine written in Rust, leading to faster build times and a more responsive development experience. +* **Simplified Configuration:** The configuration file (`tailwind.config.js` or `tailwind.config.ts`) might look different, with a more CSS-first approach. Many common customizations are now handled more intuitively. +* **Automatic Content Detection:** In many cases, explicit content path configuration is less critical due to smarter defaults. +* **First-Party Vite Plugin:** Integration with Vite is smoother with the official `@tailwindcss/vite` plugin. +* **Modern CSS Features:** v4 embraces modern CSS capabilities like cascade layers, container queries, and wide-gamut P3 colors more natively. + +## Modern Browser Requirements + +It's important to note that Tailwind CSS v4 is designed for modern browser environments. If you need to support older browsers, you might consider using Tailwind CSS v3.4 or implementing appropriate polyfills. + +Tailwind CSS v4 officially supports: + +* **Safari 16.4+** +* **Chrome 111+** +* **Firefox 128+** + +Ensure your target audience aligns with these browser versions. + +## Using Tailwind CSS in This Project + +* **Configuration:** The Tailwind CSS configuration is located at `frontend/tailwind.config.js` (or `.ts`). +* **Utility Classes:** Continue to use Tailwind's utility classes directly in your React components (TSX/JSX files) as you normally would. +* **Base Styles:** Global styles, custom base styles, or component layers can be defined in `frontend/src/index.css` (or your main CSS entry point). + +## Upgrading from v3 (General Advice) + +If you were previously using Tailwind CSS v3 and are adapting to this v4 template, or if you're upgrading another project, refer to the official [Tailwind CSS v4 Upgrade Guide](https://tailwindcss.com/docs/upgrade-guide). + +## Further Information + +For the most detailed and up-to-date information, always refer to the official [Tailwind CSS v4 Documentation](https://tailwindcss.com/docs). diff --git a/docs/index.md b/docs/index.md index 3af2ac1..41d991c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,4 +22,10 @@ To get started, explore the following sections: * **[Deployment](deployment.md):** Guidelines for deploying your application to production environments. * **[Project Roadmap](roadmap.md):** An overview of planned features and future development for this starter template. -We aim to keep this documentation up-to-date and helpful. If you find any issues or have suggestions, please feel free to contribute! \ No newline at end of file +We aim to keep this documentation up-to-date and helpful. If you find any issues or have suggestions, please feel free to contribute! + +

+ + View on GitHub + +

diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..7229a3a --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,130 @@ +# Frontend (React + Vite) + +This directory contains the frontend application built with React and Vite. + +## Overview + +- **Framework**: React +- **Build Tool**: Vite +- **Styling**: Tailwind CSS v4, Shadcn UI +- **Language**: TypeScript + +## Project Structure + +``` +frontend/ +├── public/ # Static assets +├── src/ +│ ├── assets/ # Images, fonts, etc. +│ ├── components/ # Reusable UI components +│ ├── context/ # State management (e.g., React Context) +│ ├── features/ # Feature-based components (e.g., auth, dashboard) +│ ├── hooks/ # Custom hooks +│ ├── layouts/ # Layout components (e.g., MainLayout) +│ ├── lib/ # Utility functions and constants +│ ├── pages/ # Page components (routed) +│ ├── routes/ # React Router definitions +│ ├── types/ # TypeScript types +│ ├── index.tsx # Entry point +│ ├── index.css # Global styles +│ └── vite-env.d.ts # Vite environment types +├── components.json # Shadcn UI configuration +├── index.html # Main HTML file +├── package.json # Project dependencies and scripts +├── README.md # This file +├── tailwind.config.js # Tailwind CSS configuration +├── tsconfig.json # TypeScript configuration +├── Dockerfile # Dockerfile for containerization +├── vite.config.ts # Vite configuration +└── .gitignore # Git ignore file +``` +*(Adjust the structure above to reflect the actual project layout)* + +## Getting Started + +### Prerequisites + +- Node.js (version 22.10.0 or later recommended) +- npm or yarn + +### Installation + +1. Navigate to the `frontend` directory: + ```bash + cd frontend + ``` +2. Install dependencies: + ```bash + npm install + # or + # yarn install + ``` + +### Environment Variables + +Create a `.env` file in the `frontend` directory by copying `.env.example` (if one exists) or by creating it manually. + +Key environment variables: + +- `VITE_API_URL`: The base URL for the backend API (e.g., `http://localhost:8000/api` for local development, or your production API URL). + +Example `.env`: +``` +VITE_API_URL=http://localhost:8000/api +``` + +### Available Scripts + +In the `frontend` directory, you can run the following scripts: + +- **`npm run dev` or `yarn dev`**: Runs the app in development mode. Open [http://localhost:5173](http://localhost:5173) (or the port Vite assigns) to view it in the browser. The page will reload if you make edits. + +- **`npm run build` or `yarn build`**: Builds the app for production to the `dist` folder. It correctly bundles React in production mode and optimizes the build for the best performance. + +- **`npm run lint` or `yarn lint`**: Lints the codebase using ESLint. + +- **`npm run preview` or `yarn preview`**: Serves the production build locally to preview it. + +## Styling + +This project uses Tailwind CSS for utility-first styling. +- Tailwind configuration is in `tailwind.config.js`. +- Base styles and custom CSS can be found in `src/index.css`. + +### Shadcn UI + +This project utilizes [Shadcn UI](https://ui.shadcn.com/) for its component library. Shadcn UI is not a traditional component library; instead, you install individual components into your project, giving you full control over their code and styling. + +**Adding New Components:** + +To add new Shadcn UI components, use the Shadcn UI CLI. Ensure you are in the `frontend` directory: + +```bash +npx shadcn-ui@latest add [component-name] +``` +For example, to add a button: +```bash +npx shadcn-ui@latest add button +``` +This command will add the component's source code to `src/components/ui/` (or as configured in `components.json`). + +**Configuration:** + +- The Shadcn UI configuration is in `components.json`. +- Components are typically installed into `src/components/ui`. + +**Customization:** + +Since you own the component code, you can customize components directly by editing their files in `src/components/ui/`. Tailwind CSS utility classes are used for styling, making them easy to modify to fit the project's design system. + +## Adding New Components and Pages + +- **Components**: Create new reusable components in `src/components/`. +- **Pages**: Create new page components in `src/pages/` and ensure they are added to your routing configuration (e.g., in `App.tsx` or a dedicated routes file). + +## Further Information + +- [React Documentation](https://reactjs.org/) +- [Vite Documentation](https://vitejs.dev/) +- [Tailwind CSS Documentation (v4)](https://tailwindcss.com/docs) +- [Shadcn UI Documentation](https://ui.shadcn.com/docs) diff --git a/mkdocs.yml b/mkdocs.yml index dd2a8a9..c244c48 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -5,6 +5,12 @@ nav: - Home: index.md - Initial Setup: initial_setup.md - Development: development.md + - Frontend Guides: + - Tailwind CSS v4: frontend/tailwind-v4.md + - Shadcn UI: frontend/shadcn-ui.md + - Backend Guides: + - FastAPI Overview: backend/fastapi-guide.md + - Alembic Migrations: backend/alembic-migrations.md - Make it Yours: make_it_yours.md - Deployment: deployment.md - Roadmap: roadmap.md