diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 796cbd3..0cd6e99 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,47 +9,62 @@ on: jobs: backend-test: runs-on: ubuntu-latest + services: mongo: - image: mongo:latest + image: mongo:6 ports: - 27017:27017 + steps: - - uses: actions/checkout@v3 + - name: Checkout code + uses: actions/checkout@v3 + - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.13' - - name: Install dependencies + python-version: "3.13" + + - name: Install backend dependencies run: | python -m pip install --upgrade pip pip install -r backend/requirements.txt + - name: Start Backend env: - MONGODB_URL: mongodb://localhost:27017 - SECRET_KEY: testsecret + MONGO_URI: mongodb://localhost:27017 + SECRET_KEY: test-secret run: | cd backend - python -m uvicorn app.main:app --host 0.0.0.0 --port 8001 & - sleep 10 # Wait for startup - - name: Run Verification + uvicorn app.main:app --host 0.0.0.0 --port 8000 & + sleep 8 + + - name: Verify Backend APIs run: | cd backend - python verify_backend.py + export PYTHONPATH=$(pwd) + python app/routes/api/v2/verify_db.py frontend-build: runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v3 - - name: Set up Node + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Node.js uses: actions/setup-node@v3 with: - node-version: '18' - - name: Install dependencies + node-version: "24" + cache: "npm" + cache-dependency-path: frontend/package-lock.json + + - name: Install frontend dependencies run: | cd frontend npm ci - - name: Build + + - name: Build frontend run: | cd frontend npm run build diff --git a/README.md b/README.md index 57ecf4b..7f1b8ba 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Scalable REST API with Authentication & Role-Based Access - +### *Built using Python & FastAPI* ## Project Overview This project is a scalable backend system built using Python (FastAPI) with JWT authentication and role-based access control, along with a basic frontend UI to interact with the APIs. @@ -7,25 +7,6 @@ The system allows users to register, log in, and perform CRUD operations on a se --- -## Tech Stack - -### Backend -- Language: Python -- Framework: FastAPI -- Authentication: JWT (JSON Web Tokens) -- Password Hashing: bcrypt (passlib) -- ORM: SQLAlchemy -- Database: PostgreSQL / SQLite (for demo) -- Validation: Pydantic -- API Documentation: Swagger (OpenAPI) - -### Frontend -- Framework: React.js (Vite) / Vanilla JavaScript -- HTTP Client: Axios / Fetch API -- Auth Storage: LocalStorage (JWT) - ---- - ## Project Structure ``` @@ -47,12 +28,33 @@ backend/ │ │ ├── users.py │ │ └── tasks.py │ ├── db/ -│ │ ├── session.py -│ │ └── base.py -│ └── utils/ -│ └── role_checker.py +│ │ └── mongodb.py +│ ├── utils/ +│ │ └── logger.py +│ └── Scalability_Guide.md +├── Dockerfile ├── requirements.txt └── README.md +docs/ +├── Back-End.md +├── Front-End.md +└── DB_Schema.md +frontend/ +├── src/ +│ ├── api/ +│ ├── components/ +│ ├── context/ +│ ├── pages/ +│ ├── App.tsx +│ └── main.tsx +├── package.json +├── vite.config.ts +├── Dockerfile +└── README.md +.github/ +└── workflows/ + └── ci.yml +docker-compose.yml ``` --- @@ -77,28 +79,39 @@ Authorization: Bearer ## Database Schema -### User Table -| Field | Type | -|-----------------|------------------| -| id | Integer (PK) | -| email | String (Unique) | -| hashed_password | String | -| role | USER / ADMIN | -| created_at | Timestamp | - -### Task Table -| Field | Type | -|------------|-------------------| -| id | Integer (PK) | -| title | String | -| description| String | -| owner_id | Foreign Key (User)| -| created_at | Timestamp | +### User Schema +``` +{ + "id": "Integer (PK)", + "name": "String", + "email": "String (Unique)", + "permission": "String", + "role": "USER / ADMIN", + "hashed_password": "String", + "created_at": "Timestamp" +} +``` + +### Task Schema +``` +{ + "id": "Integer (PK)", + "title": "String", + "description": "String", + "owner_id": "Foreign Key (User)", + "created_at": "Timestamp" +} +``` --- ## API Endpoints +### Health Check APIs +| Method | Endpoint | Description | +|--------|------------------------|------------------| +| GET | /api/v1/health | Health check | + ### Auth APIs | Method | Endpoint | Description | |--------|------------------------|------------------| @@ -108,10 +121,29 @@ Authorization: Bearer ### Task APIs | Method | Endpoint | Access | |--------|----------------------|--------| -| POST | /api/v1/tasks | User | -| GET | /api/v1/tasks | User | -| PUT | /api/v1/tasks/{id} | Owner | +| GET | /api/v1/tasks | User | +| POST | /api/v1/tasks | User | +| PUT | /api/v1/tasks/{id} | Owner | | DELETE | /api/v1/tasks/{id} | Admin | +| GET | /api/v1/tasks | User | + +### User APIs +| Method | Endpoint | Access | +|--------|----------------------|--------| +| GET | /api/v1/users | User | +| POST | /api/v1/users | User | +| PUT | /api/v1/users/{id} | Owner | +| DELETE | /api/v1/users/{id} | Admin | +| GET | /api/v1/users | Admin | + +--- + +## Docker Support + +The project includes Dockerfiles for both backend and frontend. +Run both services using: + +**``` docker-compose up --build ```** --- @@ -119,7 +151,7 @@ Authorization: Bearer Swagger UI is available at: ```bash -http://localhost:8000/docs +http://localhost:8001/docs ``` A Postman collection is also provided for API testing. @@ -147,12 +179,14 @@ A Postman collection is also provided for API testing. ### Backend Setup ```bash -git clone +git clone https://github.com/HackyCoder0951/authdb.git +python -m venv .venv cd backend -python -m venv venv -source venv/bin/activate +source .venv/bin/activate pip install -r requirements.txt -uvicorn app.main:app --reload +# Ensure MongoDB is running locally or set MONGO_URI in .env +uvicorn app.main:app --host 0.0.0.0 --port 8001 + ``` ### Frontend Setup @@ -174,12 +208,24 @@ npm run dev --- ## Scalability & Future Improvements -- Microservices-based architecture -- Redis caching for frequently accessed data -- Load balancing using NGINX +- **Scalability Guide**: A detailed guide (`app/Scalability_Guide.md`) is included to assist with understanding microservices, caching (Redis), and load balancing. +- Microservices-based architecture (Proposed) +- Redis caching for frequently accessed data (Proposed) +- Load balancing using NGINX (Proposed) +- **Advanced Logging**: Custom logging utility for better observability. - Docker containerization - API rate limiting -- CI/CD pipeline integration +## Detailed Documentation +For deep dives into specific areas, please refer to the `docs/` directory: +- **[Backend Architecture](docs/Back-End.md)**: Logic flow, Auth, and Task management. +- **[Frontend Architecture](docs/Front-End.md)**: Component structure and State management. +- **[Database Schema](docs/DB_Schema.md)**: ERD diagrams and Collection details. + +## CI/CD Pipeline +Automated testing and build pipelines are implemented using **GitHub Actions**. +- **Config**: `.github/workflows/ci.yml` +- **Backend**: Runs unit tests and verifies API health. +- **Frontend**: Installs dependencies and checks build status. --- @@ -192,9 +238,3 @@ npm run dev - API documentation --- - -## Author -Backend Developer Intern Assignment - -Built using Python & FastAPI - diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..dcc735c --- /dev/null +++ b/backend/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 app ./app + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8001"] diff --git a/backend/app/main.py b/backend/app/main.py index 7f00aeb..eb33194 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,18 +1,14 @@ -import logging from fastapi import FastAPI +from app.routes.api.v1 import auth, tasks, users, health from fastapi.middleware.cors import CORSMiddleware from app.core.config import settings from app.db.mongodb import mongodb -from app.routes import auth, tasks, users +from app.utils.logger import setup_logging -# Configure Logging -logging.basicConfig( - level=logging.INFO, - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", -) -logger = logging.getLogger(__name__) +# Configure Logging using custom utility +logger = setup_logging() -app = FastAPI(title=settings.PROJECT_NAME) +app = FastAPI(title=settings.PROJECT_NAME, redirect_slashes=False) # CORS app.add_middleware( @@ -36,14 +32,7 @@ async def shutdown_db_client(): await mongodb.close_database_connection() logger.info("Database connection closed.") -# Routes -@app.get("/health", tags=["Health"]) -async def health_check(): - is_connected = await mongodb.check_connection() - if is_connected: - return {"status": "ok", "database": "connected"} - return {"status": "error", "database": "disconnected"} - +app.include_router(health.router, prefix="/api/v1", tags=["Health"]) app.include_router(auth.router, prefix="/api/v1/auth", tags=["Auth"]) app.include_router(tasks.router, prefix="/api/v1/tasks", tags=["Tasks"]) app.include_router(users.router, prefix="/api/v1/users", tags=["Users"]) diff --git a/backend/app/routes/auth.py b/backend/app/routes/api/v1/auth.py similarity index 100% rename from backend/app/routes/auth.py rename to backend/app/routes/api/v1/auth.py diff --git a/backend/app/routes/api/v1/health.py b/backend/app/routes/api/v1/health.py new file mode 100644 index 0000000..c26c904 --- /dev/null +++ b/backend/app/routes/api/v1/health.py @@ -0,0 +1,12 @@ +from fastapi import APIRouter +from app.db.mongodb import mongodb + +router = APIRouter() + +# Routes +@router.get("/health", tags=["Health"]) +async def health_check(): + is_connected = await mongodb.check_connection() + if is_connected: + return {"status": "ok", "database": "connected"} + return {"status": "error", "database": "disconnected"} \ No newline at end of file diff --git a/backend/app/routes/tasks.py b/backend/app/routes/api/v1/tasks.py similarity index 100% rename from backend/app/routes/tasks.py rename to backend/app/routes/api/v1/tasks.py diff --git a/backend/app/routes/users.py b/backend/app/routes/api/v1/users.py similarity index 100% rename from backend/app/routes/users.py rename to backend/app/routes/api/v1/users.py diff --git a/backend/verify_db.py b/backend/app/routes/api/v2/verify_db.py similarity index 90% rename from backend/verify_db.py rename to backend/app/routes/api/v2/verify_db.py index 927a287..057bc1e 100644 --- a/backend/verify_db.py +++ b/backend/app/routes/api/v2/verify_db.py @@ -1,7 +1,6 @@ import asyncio from motor.motor_asyncio import AsyncIOMotorClient from app.core.config import settings -# import certifi async def verify(): print(f"Connecting to {settings.MONGODB_URL}...") @@ -13,4 +12,4 @@ async def verify(): print(f"FAILURE: {e}") if __name__ == "__main__": - asyncio.run(verify()) + asyncio.run(verify()) \ No newline at end of file diff --git a/backend/app/utils/logger.py b/backend/app/utils/logger.py new file mode 100644 index 0000000..9d71abd --- /dev/null +++ b/backend/app/utils/logger.py @@ -0,0 +1,56 @@ +import logging +import sys + +# ASCII colors for console +RESET_COLOR = "\033[0m" +GREEN = "\033[32m" +YELLOW = "\033[33m" +RED = "\033[31m" +CYAN = "\033[36m" +WHITE = "\033[37m" + +class ColoredFormatter(logging.Formatter): + """Custom formatter to add colors to log levels for better readability.""" + + FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + + FORMATS = { + logging.DEBUG: WHITE + FORMAT + RESET_COLOR, + logging.INFO: GREEN + FORMAT + RESET_COLOR, + logging.WARNING: YELLOW + FORMAT + RESET_COLOR, + logging.ERROR: RED + FORMAT + RESET_COLOR, + logging.CRITICAL: RED + "\033[1m" + FORMAT + RESET_COLOR, # Bold Red + } + + def format(self, record): + log_fmt = self.FORMATS.get(record.levelno, self.FORMAT) + formatter = logging.Formatter(log_fmt, datefmt="%Y-%m-%d %H:%M:%S") + return formatter.format(record) + +def setup_logging(level: int = logging.INFO): + """ + Sets up the global logging configuration with colored output. + should be called once at application startup. + """ + # Get root logger + root_logger = logging.getLogger() + root_logger.setLevel(level) + + # Clear existing handlers to prevent duplicate logs (common with uvicorn/fastapi) + if root_logger.hasHandlers(): + root_logger.handlers.clear() + + # Create console handler + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setLevel(level) + + # Apply custom colored formatter + console_handler.setFormatter(ColoredFormatter()) + + # Add handler to root logger + root_logger.addHandler(console_handler) + + # Optional: Adjust third-party loggers if they are too noisy + # logging.getLogger("uvicorn.access").setLevel(logging.WARNING) + + return root_logger diff --git a/backend/verify_backend.py b/backend/verify_backend.py deleted file mode 100644 index f93ec8b..0000000 --- a/backend/verify_backend.py +++ /dev/null @@ -1,116 +0,0 @@ -import requests -import time - -BASE_URL = "http://localhost:8001/api/v1" - -def test_backend(): - print("Starting Backend Verification...") - - # Unique email for every run - timestamp = int(time.time()) - admin_email = f"admin_{timestamp}@example.com" - user_email = f"user_{timestamp}@example.com" - password = "password123" - - # 1. Register Admin - print(f"1. Registering Admin ({admin_email})...") - res = requests.post(f"{BASE_URL}/auth/register", json={ - "email": admin_email, - "password": password, - "role": "ADMIN" - }) - if res.status_code != 201: - print(f"Failed to register admin: {res.text}") - return - print(" Success") - - # 2. Register Regular User - print(f"2. Registering User ({user_email})...") - res = requests.post(f"{BASE_URL}/auth/register", json={ - "email": user_email, - "password": password, - "role": "USER" - }) - if res.status_code != 201: - print(f"Failed to register user: {res.text}") - return - print(" Success") - - # 3. Login Admin - print("3. Logging in Admin...") - res = requests.post(f"{BASE_URL}/auth/login", data={ - "username": admin_email, - "password": password - }) - if res.status_code != 200: - print(f"Failed to login admin: {res.text}") - return - admin_token = res.json()["access_token"] - print(" Success") - - # 4. Login User - print("4. Logging in User...") - res = requests.post(f"{BASE_URL}/auth/login", data={ - "username": user_email, - "password": password - }) - if res.status_code != 200: - print(f"Failed to login user: {res.text}") - return - user_token = res.json()["access_token"] - print(" Success") - - # Headers - admin_headers = {"Authorization": f"Bearer {admin_token}"} - user_headers = {"Authorization": f"Bearer {user_token}"} - - # 5. User Create Task - print("5. User Creating Task...") - res = requests.post(f"{BASE_URL}/tasks/", headers=user_headers, json={ - "title": "User Task", - "description": "This is a task" - }) - if res.status_code != 201: - print(f"Failed to create task: {res.text}") - return - task_id = res.json()["_id"] - print(" Success") - - # 6. User Read Tasks - print("6. User Reading Tasks...") - res = requests.get(f"{BASE_URL}/tasks/", headers=user_headers) - if res.status_code != 200 or len(res.json()) == 0: - print(f"Failed to read tasks: {res.text}") - return - print(" Success") - - # 7. Admin Read Tasks (RBAC Check) - # Admin viewing generic lists might not see everyone's task unless using /all endpoint - # Let's check the /all endpoint - print("7. Admin Reading All Tasks...") - res = requests.get(f"{BASE_URL}/tasks/all", headers=admin_headers) - if res.status_code != 200: - print(f"Failed to read all tasks as admin: {res.text}") - return - # Check if task_id is in the list - all_tasks = res.json() - if not any(t["_id"] == task_id for t in all_tasks): - print(" Admin did not see the user task!") - return - print(" Success") - - # 8. Admin Delete Task - print("8. Admin Deleting User Task...") - res = requests.delete(f"{BASE_URL}/tasks/{task_id}", headers=admin_headers) - if res.status_code != 200: - print(f"Failed to delete task: {res.text}") - return - print(" Success") - - print("\nVerification Complete!") - -if __name__ == "__main__": - try: - test_backend() - except Exception as e: - print(f"An error occurred: {e}") diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a5e365d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +version: "3.9" + +services: + backend: + build: ./backend + ports: + - "8001:8001" + + frontend: + build: ./frontend + ports: + - "5173:5173" + depends_on: + - backend diff --git a/docs/Back-End.md b/docs/Back-End.md new file mode 100644 index 0000000..cd277e0 --- /dev/null +++ b/docs/Back-End.md @@ -0,0 +1,85 @@ +# Backend Documentation + +## Overview +The backend is a high-performance REST API built with **FastAPI** and **MongoDB**. It handles user authentication, task management, and enforces role-based access control. + +## Tech Stack +- **Framework**: FastAPI (Python 3.10+) +- **Database**: MongoDB (Motor / Pymongo) +- **Authentication**: JWT (JSON Web Tokens) with OAuth2 Password Bearer +- **Validation**: Pydantic Models +- **Security**: Passlib (Bcrypt) + +## Architecture + +The backend follows a modular architecture separating routes, business logic, and database interactions. + +```mermaid +graph TD + Client[Client / Frontend] -->|HTTP Requests| API[FastAPI Application] + + subgraph "API Layer" + API --> AuthRouter[Auth Routes] + API --> UserRouter[User Routes] + API --> TaskRouter[Task Routes] + end + + subgraph "Service & Logic Layer" + AuthRouter --> AuthController[Auth Logic] + UserRouter --> UserCRUD[User CRUD] + TaskRouter --> TaskCRUD[Task CRUD] + end + + subgraph "Data Layer" + AuthController --> DB[(MongoDB)] + UserCRUD --> DB + TaskCRUD --> DB + end + + TaskRouter -.->|Depends| AuthController +``` + +## Authentication Flow + +We use JWT for stateless authentication. + +```mermaid +sequenceDiagram + participant U as User + participant A as Auth API + participant D as Database + + U->>A: POST /auth/login (email, password) + A->>D: Find User by Email + D-->>A: User Data + A->>A: Verify Password Hash + alt Valid Credentials + A->>A: Generate JWT Access Token + A-->>U: Return Token {access_token, token_type} + else Invalid + A-->>U: 401 Unauthorized + end + + Note over U, A: Subsequent Requests + U->>A: GET /tasks (Header: Bearer Token) + A->>A: Decode & Verify Token + A->>D: Fetch User (Dependency Injection) + A-->>U: Protected Data +``` + +## Key Components + +### 1. Database Connection (`app/db/mongodb.py`) +Handles the asynchronous connection to MongoDB using `motor`. It connects on startup and closes on shutdown. + +### 2. Authentication (`app/routes/auth.py`) +- **Login**: Validates credentials and returns a JWT. +- **Register**: Creates a new user with a hashed password. + +### 3. Dependencies (`app/core/dependencies.py`) +- `get_current_user`: Decodes the JWT from the request header and retrieves the user context. This is what secures the endpoints. + +### 4. Task Management (`app/routes/tasks.py`) +- Implements CRUD operations. +- Enforces ownership: Users can only see/edit their own tasks. +- **Admin Override**: Admins can see/delete all tasks. diff --git a/docs/DB_Schema.md b/docs/DB_Schema.md new file mode 100644 index 0000000..6603198 --- /dev/null +++ b/docs/DB_Schema.md @@ -0,0 +1,57 @@ +# Database Documentation + +## Overview +The application uses **MongoDB** as its primary data store. Due to the document-oriented nature of MongoDB, we do not have rigid tables, but we enforce schemas using **Pydantic Models** in the application layer. + +## Entity Relationship Diagram (ERD) + +Although MongoDB is NoSQL, the entities have implicit relationships. + +```mermaid +erDiagram + USERS ||--|{ TASKS : "owns" + + USERS { + ObjectId _id PK + string email UK + string hashed_password + string name + enum role "USER | ADMIN" + list permissions + datetime created_at + } + + TASKS { + ObjectId _id PK + string title + string description + string owner_id FK "Reference to USERS._id" + datetime created_at + } +``` + +## Collection Details + +### 1. Users Collection (`users`) +Stores all registered user information. +- **_id**: Automatically generated MongoDB ObjectId. +- **email**: Unique index ensures no duplicate registrations. +- **role**: Determines access level. Defaults to `USER`. +- **permissions**: List of granular permission strings (extensible). + +### 2. Tasks Collection (`tasks`) +Stores to-do items for users. +- **_id**: Automatically generated MongoDB ObjectId. +- **owner_id**: A string representation of the User's ObjectId. This links the task to a specific user. +- **Index**: Typically indexed on `owner_id` for fast retrieval of a user's tasks. + +## Implementation Details + +### Model Validation +We use Pydantic models to validate data before it enters the database. +- **Location**: `backend/app/models/` +- **Files**: `user.py`, `task.py` + +### ID Handling +MongoDB uses `_id` (ObjectId), but API clients typically expect `id` (string). +- Our Pydantic models use `Field(alias="_id")` to map the database `_id` to the API `id` field automatically. diff --git a/docs/Front-End.md b/docs/Front-End.md new file mode 100644 index 0000000..c5cdfac --- /dev/null +++ b/docs/Front-End.md @@ -0,0 +1,73 @@ +# Frontend Documentation + +## Overview +The frontend is a modern Single Page Application (SPA) built with **React 19**, **Vite**, and **TypeScript**. It provides a responsive interface for users to manage their tasks and keeps track of authentication state using React Context. + +## Tech Stack +- **Framework**: React 19 +- **Build Tool**: Vite +- **Language**: TypeScript +- **State Management**: React Context API +- **Routing**: React Router DOM v7 +- **HTTP Client**: Axios + +## Component Architecture + +The application is structured around a central Authentication Context that provides user state to all pages. + +```mermaid +graph TD + App[App Component] --> AuthProvider[Auth Context Provider] + AuthProvider --> Router[App Router] + + subgraph "Public Routes" + Router --> Login[Login Page] + Router --> Register[Register Page] + Router --> Landing[Landing Page] + end + + subgraph "Protected Routes" + Router --> Protected{Protected Route Wrapper} + Protected --> Dashboard[Dashboard / Task List] + Protected --> Profile[User Profile] + end + + Dashboard --> TaskList[Task List Component] + Dashboard --> AddTask[Add Task Form] +``` + +## Implementation Details + +### 1. Authentication Context (`src/context/AuthContext.tsx`) +This is the heart of the frontend state. +- **State**: `user` (User object | null), `token` (string | null), `isAuthenticated` (boolean). +- **Actions**: `login(token)`, `logout()`. +- **Persistance**: Checks `localStorage` on load to restore the session. + +### 2. API Integration (`src/api/axios.ts`) +We use a centralized Axios instance. +- **Interceptors**: Automatically attaches the `Authorization: Bearer ` header to every request if a token exists in the context/storage. +- **Base URL**: Configured to point to the FastAPI backend. + +### 3. Protected Routes +A wrapper component checks the `isAuthenticated` flag. +- **If True**: Renders the child component (e.g., Dashboard). +- **If False**: Redirects to `/login`. + +### 4. Task Management Flow + +```mermaid +sequenceDiagram + participant U as User + participant UI as React Component + participant C as Auth Context + participant API as Axios/Backend + + U->>UI: Clicks "Add Task" + UI->>API: POST /tasks (payload) + activate API + API-->>UI: 201 Created (New Task Data) + deactivate API + UI->>UI: Update Local State (Tasks Array) + UI-->>U: Show "Task Added" Toast +``` diff --git a/docs/Scalability_Guide.md b/docs/Scalability_Guide.md new file mode 100644 index 0000000..32a135b --- /dev/null +++ b/docs/Scalability_Guide.md @@ -0,0 +1,111 @@ +# Scalability Implementation Guide for AuthDB + +This is how we can scale the current Monolithic FastAPI application into a scalable, distributed system.
It includes visual diagrams to help you conceptualize and explain these architectures during an interview. + +--- + +## 1. Current Architecture (Monolith) vs. Microservices (Split Logic) + +**"Splitting authentication and business logic into separate microservices."** + +### Concept: "The One-Man Show" vs. "Specialized Teams" +Currently, your `backend/app/main.py` is a monolith. It handles everything. We want to split it. + +### Visual Diagram + +```mermaid +graph TD + subgraph "Monolithic Architecture (Current)" + Client1[User] --> Monolith["FastAPI App (Auth + Tasks)"] + Monolith --> DB[(MongoDB)] + end + + subgraph "Microservices Architecture (Proposed)" + Client2[User] --> Gateway["API Gateway / Nginx"] + Gateway -->|/auth| AuthService["Auth Service (Port 8001)"] + Gateway -->|/tasks| TaskService["Task Service (Port 8002)"] + + AuthService --> DB + TaskService --> DB + end +``` + +**Explanation**: +"Currently, my app is a Monolith where `main.py` handles both Auth and Tasks. I would split this into two services: +1. **Auth Service**: Handles login/registration (Port 8001). +2. **Task Service**: Handles todo lists (Port 8002). +This allows me to scale the Task service independently if it gets heavy traffic." + +--- + +## 2. Caching Strategy (Redis) + +**"Redis can be used for caching frequent reads."** + +### Concept: "The Sticky Note" +Database queries are slow. Redis (memory) is fast. We check Redis first. + +### Visual Diagram + +```mermaid +sequenceDiagram + participant User + participant App as Task Service + participant Redis as Redis Cache + participant DB as MongoDB + + User->>App: GET /tasks (Fetch Tasks) + App->>Redis: Check if tasks exist in cache? + alt Cache Hit (Found) + Redis-->>App: Return cached tasks + App-->>User: Return tasks (Fast! ~5ms) + else Cache Miss (Not Found) + Redis-->>App: Null + App->>DB: Query tasks from DB + DB-->>App: Return tasks data + App->>Redis: Save tasks to cache (for next time) + App-->>User: Return tasks (Slower ~100ms) + end +``` + +**Explanation**: +"I would use Redis to cache the list of tasks. When a user creates a task (`cache invalidation`), I clear the cache so they always see fresh data. This drastically reduces the load on MongoDB." + +--- + +## 3. Load Balancing + +**"Load balancing can distribute traffic across multiple instances."** + +### Concept: "The Traffic Cop" +Run multiple copies of your code and distribute users among them. + +### Visual Diagram + +```mermaid +graph TD + User[User Traffic] --> Nginx["Load Balancer (Nginx)"] + + subgraph "Task Service Instances" + Nginx --> Instance1["Instance 1 (Port 8002)"] + Nginx --> Instance2["Instance 2 (Port 8003)"] + Nginx --> Instance3["Instance 3 (Port 8004)"] + end + + Instance1 --> Redis["Redis"] + Instance2 --> Redis + Instance3 --> Redis +``` + +**Explanation**: +"To handle more users, I would run 3 instances of my Task Service using Docker. A Load Balancer (like Nginx) sits in front and distributes requests. If Instance 1 is busy, it sends the user to Instance 2." + +--- + +## Summary Table for Interview + +| Concept | The "What" | The "Why" | In Your Code | +| :--- | :--- | :--- | :--- | +| **Microservices** | Splitting `main.py` into `auth.py` and `tasks.py` servers. | Independent scaling & fault isolation. | `AuthService` handles tokens; `TaskService` verifies them. | +| **Redis** | Storing `GET /tasks` results in RAM. | Speed (ms vs s) & database protection. | Check Redis before `await db.tasks.find()`. | +| **Load Balancing** | Nginx distributing requests to 3 Docker containers. | Handling high traffic & redundancy. | Traffic -> Nginx -> [Container A, B, C]. | diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..7fab5ca --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,12 @@ +FROM node:24-alpine + +WORKDIR /app + +COPY package*.json ./ +RUN npm install + +COPY . . +RUN npm run build + +EXPOSE 5173 +CMD ["npm", "run", "dev"] diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index d2e7761..0000000 --- a/frontend/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# React + TypeScript + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - -## React Compiler - -The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: - -```js -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - - // Remove tseslint.configs.recommended and replace with this - tseslint.configs.recommendedTypeChecked, - // Alternatively, use this for stricter rules - tseslint.configs.strictTypeChecked, - // Optionally, add this for stylistic rules - tseslint.configs.stylisticTypeChecked, - - // Other configs... - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) -``` - -You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: - -```js -// eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' - -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - // Enable lint rules for React - reactX.configs['recommended-typescript'], - // Enable lint rules for React DOM - reactDom.configs.recommended, - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) -``` diff --git a/frontend/src/components/GlobalAxiosInterceptor.tsx b/frontend/src/components/GlobalAxiosInterceptor.tsx index efebd84..fb81271 100644 --- a/frontend/src/components/GlobalAxiosInterceptor.tsx +++ b/frontend/src/components/GlobalAxiosInterceptor.tsx @@ -63,7 +63,7 @@ export const GlobalAxiosInterceptor: React.FC = () => { // 5. Conflicts (409) - e.g. Duplicate Email else if (status === 409) { - showToast(backendMessage || 'Conflict. Resource already exists.', 'error'); + showToast(backendMessage || 'Resource already exists.', 'error'); } // 6. Bad Request (400) - e.g. Validation logic that isn't Pydantic default diff --git a/frontend/src/index.css b/frontend/src/index.css index 8726ee9..a953753 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,7 +1,7 @@ :root { --primary-color: #4f46e5; --secondary-color: #818cf8; - --bg-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + --bg-gradient: linear-gradient(135deg, #202f74 0%, #02074b 100%); --glass-bg: rgba(255, 255, 255, 0.1); --glass-border: rgba(255, 255, 255, 0.2); --text-color: #ffffff; diff --git a/frontend/src/pages/AdminPanel.tsx b/frontend/src/pages/AdminPanel.tsx index eca3e19..0889874 100644 --- a/frontend/src/pages/AdminPanel.tsx +++ b/frontend/src/pages/AdminPanel.tsx @@ -50,7 +50,7 @@ const AdminPanel: React.FC = () => { const checkApiStatus = async () => { setApiStatus('checking'); const start = Date.now(); - const url = 'http://localhost:8001/health'; + const url = 'http://localhost:8001/api/v1/health'; try { await api.get(url); // Relative to /api/v1, so ../health hits /health // Actually, axios baseURL is /api/v1. Health is at root /health usually or /api/v1/health? diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 7de2511..60e1537 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -35,7 +35,7 @@ const Dashboard: React.FC = () => { const checkApiStatus = async () => { setApiStatus('checking'); const start = Date.now(); - const url = 'http://localhost:8001/health'; + const url = 'http://localhost:8001/api/v1/health'; try { await api.get(url); // Relative to /api/v1, so ../health hits /health // Actually, axios baseURL is /api/v1. Health is at root /health usually or /api/v1/health? diff --git a/render.yaml b/render.yaml deleted file mode 100644 index 5bcbb55..0000000 --- a/render.yaml +++ /dev/null @@ -1,29 +0,0 @@ -services: - # Backend Service - - type: web - name: authdb-backend - env: python - buildCommand: pip install -r backend/requirements.txt - startCommand: cd backend && uvicorn app.main:app --host 0.0.0.0 --port $PORT - envVars: - - key: PTHON_VERSION - value: 3.13.0 - - key: SECRET_KEY - generateValue: true - - key: MONGODB_URL - sync: false - autoDeploy: false - - # Frontend Service - - type: static - name: authdb-frontend - env: static - buildCommand: cd frontend && npm install && npm run build - staticPublishPath: frontend/dist - envVars: - - key: VITE_API_URL - fromService: - type: web - name: authdb-backend - envVar: RENDER_EXTERNAL_URL - autoDeploy: false