-
Notifications
You must be signed in to change notification settings - Fork 0
Implement complete app scaffolding with Next.js 15 frontend and FastAPI backend #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # Dependencies | ||
| node_modules/ | ||
| __pycache__/ | ||
| *.pyc | ||
| *.pyo | ||
| *.pyd | ||
| .Python | ||
| env/ | ||
| venv/ | ||
| .venv/ | ||
|
|
||
| # Database | ||
| *.db | ||
| *.sqlite | ||
| *.sqlite3 | ||
|
|
||
| # IDE | ||
| .vscode/ | ||
| .idea/ | ||
| *.swp | ||
| *.swo | ||
|
|
||
| # OS | ||
| .DS_Store | ||
| Thumbs.db | ||
|
|
||
| # Logs | ||
| *.log | ||
| logs/ | ||
|
|
||
| # Environment variables | ||
| .env | ||
| .env.local | ||
| .env.production.local | ||
| .env.test.local | ||
|
|
||
| # Build outputs | ||
| .next/ | ||
| dist/ | ||
| build/ | ||
|
|
||
| # Cache | ||
| .cache/ | ||
| .parcel-cache/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,130 @@ | ||
| # ai-book-reader | ||
| AI assisted book reader | ||
| # AI Book Reader | ||
|
|
||
| AI assisted book reader with smart notes and bookmarks management. | ||
|
|
||
| ## Features | ||
|
|
||
| - 📝 **Smart Notes**: Take AI-powered notes while reading | ||
| - 🔖 **Bookmarks**: Save important passages and references | ||
| - 🤖 **AI Insights**: Get AI-generated summaries and insights (coming soon) | ||
| - 💾 **SQLite Database**: Local storage for notes and bookmarks | ||
|
|
||
| ## Tech Stack | ||
|
|
||
| ### Frontend | ||
| - **Next.js 15** with TypeScript | ||
| - **React 19** | ||
| - **Tailwind CSS** for styling | ||
| - **App Router** for navigation | ||
|
|
||
| ### Backend | ||
| - **FastAPI** with Python 3.12 | ||
| - **SQLAlchemy** for database ORM | ||
| - **SQLite** for data storage | ||
| - **Pydantic** for data validation | ||
| - **CORS** enabled for frontend integration | ||
|
|
||
| ## Project Structure | ||
|
|
||
| ``` | ||
| ai-book-reader/ | ||
| ├── frontend/ # Next.js 15 frontend application | ||
| │ ├── app/ # App router pages and components | ||
| │ │ ├── notes/ # Notes management page | ||
| │ │ ├── bookmarks/# Bookmarks management page | ||
| │ │ └── ... | ||
| │ └── package.json # Frontend dependencies | ||
| ├── backend/ # FastAPI backend application | ||
| │ ├── app/ # Main application code | ||
| │ │ ├── main.py # FastAPI app and routes | ||
| │ │ ├── models.py# SQLAlchemy database models | ||
| │ │ ├── schemas.py# Pydantic schemas | ||
| │ │ └── database.py# Database configuration | ||
| │ └── requirements.txt# Backend dependencies | ||
| ├── vercel.json # Vercel deployment configuration | ||
| └── start.sh # Local development script | ||
| ``` | ||
|
|
||
| ## Getting Started | ||
|
|
||
| ### Prerequisites | ||
| - Node.js 20+ | ||
| - Python 3.12+ | ||
| - npm or yarn | ||
|
|
||
| ### Installation | ||
|
|
||
| 1. **Install Frontend Dependencies** | ||
| ```bash | ||
| cd frontend | ||
| npm install | ||
| ``` | ||
|
|
||
| 2. **Install Backend Dependencies** | ||
| ```bash | ||
| cd backend | ||
| pip install -r requirements.txt | ||
| ``` | ||
|
|
||
| ### Development | ||
|
|
||
| #### Option 1: Start Both Servers with Script | ||
| ```bash | ||
| chmod +x start.sh | ||
| ./start.sh | ||
| ``` | ||
|
|
||
| #### Option 2: Start Servers Individually | ||
|
|
||
| **Backend (Terminal 1):** | ||
| ```bash | ||
| cd backend | ||
| python3 -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 | ||
| ``` | ||
|
|
||
| **Frontend (Terminal 2):** | ||
| ```bash | ||
| cd frontend | ||
| npm run dev | ||
| ``` | ||
|
|
||
| ### Access the Application | ||
|
|
||
| - **Frontend**: http://localhost:3000 | ||
| - **Backend API**: http://localhost:8000 | ||
| - **API Documentation**: http://localhost:8000/docs | ||
|
|
||
| ## API Endpoints | ||
|
|
||
| ### Notes | ||
| - `GET /notes/` - List all notes | ||
| - `POST /notes/` - Create a new note | ||
| - `GET /notes/{id}` - Get specific note | ||
| - `PUT /notes/{id}` - Update note | ||
| - `DELETE /notes/{id}` - Delete note | ||
|
|
||
| ### Bookmarks | ||
| - `GET /bookmarks/` - List all bookmarks | ||
| - `POST /bookmarks/` - Create a new bookmark | ||
| - `GET /bookmarks/{id}` - Get specific bookmark | ||
| - `DELETE /bookmarks/{id}` - Delete bookmark | ||
|
|
||
| ## Deployment | ||
|
|
||
| This project is configured for deployment on **Vercel**: | ||
|
|
||
| 1. Connect your GitHub repository to Vercel | ||
| 2. Vercel will automatically detect the configuration from `vercel.json` | ||
| 3. Both frontend and backend will be deployed as serverless functions | ||
|
|
||
| ## Contributing | ||
|
|
||
| 1. Fork the repository | ||
| 2. Create a feature branch | ||
| 3. Make your changes | ||
| 4. Test locally | ||
| 5. Submit a pull request | ||
|
|
||
| ## License | ||
|
|
||
| This project is open source and available under the MIT License. | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,28 @@ | ||||||||||||||||||||||||||||||||||||
| from fastapi import FastAPI | ||||||||||||||||||||||||||||||||||||
| from fastapi.middleware.cors import CORSMiddleware | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| app = FastAPI( | ||||||||||||||||||||||||||||||||||||
| title="AI Book Reader API", | ||||||||||||||||||||||||||||||||||||
| description="API for managing notes and bookmarks in the AI Book Reader", | ||||||||||||||||||||||||||||||||||||
| version="1.0.0" | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major 🧩 Analysis chainVerify consistency with backend/app/main.py. This file creates a separate FastAPI app instance, but the PR context indicates that Run the following script to check for duplicate FastAPI app instances: 🏁 Script executed: #!/bin/bash
# Description: Find all FastAPI app instance declarations
rg -n "app = FastAPI\(" --type=pyLength of output: 67 🏁 Script executed: #!/bin/bash
sed -n '1,200p' api/index.py
echo "---"
sed -n '1,200p' backend/app/main.pyLength of output: 4384 Consolidate FastAPI app definition 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| # Add CORS middleware | ||||||||||||||||||||||||||||||||||||
| app.add_middleware( | ||||||||||||||||||||||||||||||||||||
| CORSMiddleware, | ||||||||||||||||||||||||||||||||||||
| allow_origins=["*"], | ||||||||||||||||||||||||||||||||||||
| allow_credentials=True, | ||||||||||||||||||||||||||||||||||||
| allow_methods=["*"], | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+11
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Initializing Useful? React with 👍 / 👎. |
||||||||||||||||||||||||||||||||||||
| allow_headers=["*"], | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+11
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical security issue: Overly permissive CORS configuration. The CORS configuration allows all origins (
Apply this diff to fix the security issue: app.add_middleware(
CORSMiddleware,
- allow_origins=["*"],
+ allow_origins=[
+ "http://localhost:3000", # Local development
+ # Add production frontend URLs here
+ ],
allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
+ allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
+ allow_headers=["Content-Type", "Authorization"],
)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @app.get("/") | ||||||||||||||||||||||||||||||||||||
| def read_root(): | ||||||||||||||||||||||||||||||||||||
| return {"message": "AI Book Reader API", "version": "1.0.0"} | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @app.get("/health") | ||||||||||||||||||||||||||||||||||||
| def health_check(): | ||||||||||||||||||||||||||||||||||||
| return {"status": "healthy"} | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| # Export the app for Vercel | ||||||||||||||||||||||||||||||||||||
| handler = app | ||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| # AI Book Reader Backend Package |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| from app.main import app | ||
|
|
||
| # This is the Vercel serverless function handler | ||
| def handler(request, response): | ||
| return app(request, response) | ||
|
Comment on lines
+1
to
+5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Incorrect serverless handler implementation for FastAPI. This handler implementation has two critical issues:
For Vercel deployment of FastAPI, use the Mangum adapter which properly bridges ASGI to AWS Lambda/Vercel's handler signature: -from app.main import app
+from mangum import Mangum
+from backend.app.main import app
-# This is the Vercel serverless function handler
-def handler(request, response):
- return app(request, response)
+# Vercel serverless function handler using Mangum adapter
+handler = Mangum(app, lifespan="off")Add fastapi>=0.115.0
uvicorn[standard]>=0.32.0
sqlalchemy>=2.0.36
pydantic>=2.10.0
python-multipart>=0.0.17
httpx>=0.28.0
+mangum>=0.19.0Verify the Vercel configuration routes to this handler correctly: #!/bin/bash
# Description: Check Vercel configuration for API routing
echo "=== Checking vercel.json for API routing configuration ==="
if [ -f "vercel.json" ]; then
cat vercel.json | jq '.routes, .rewrites, .builds' 2>/dev/null || cat vercel.json
else
echo "vercel.json not found"
fi🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| # AI Book Reader Backend |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,24 @@ | ||||||||||||||||||||||||||||||||||||||||||||
| from sqlalchemy import create_engine | ||||||||||||||||||||||||||||||||||||||||||||
| from sqlalchemy.ext.declarative import declarative_base | ||||||||||||||||||||||||||||||||||||||||||||
| from sqlalchemy.orm import sessionmaker | ||||||||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove unused import. The from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
-import os📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| # SQLite database URL | ||||||||||||||||||||||||||||||||||||||||||||
| SQLALCHEMY_DATABASE_URL = "sqlite:///./ai_book_reader.db" | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| # Create SQLite engine | ||||||||||||||||||||||||||||||||||||||||||||
| engine = create_engine( | ||||||||||||||||||||||||||||||||||||||||||||
| SQLALCHEMY_DATABASE_URL, | ||||||||||||||||||||||||||||||||||||||||||||
| connect_args={"check_same_thread": False} | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+6
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Make database URL configurable via environment variables. The database URL is hardcoded, which prevents using different databases for development, testing, and production environments. +import os
+
# SQLite database URL
-SQLALCHEMY_DATABASE_URL = "sqlite:///./ai_book_reader.db"
+SQLALCHEMY_DATABASE_URL = os.getenv(
+ "DATABASE_URL",
+ "sqlite:///./ai_book_reader.db"
+)
# Create SQLite engine
engine = create_engine(
- SQLALCHEMY_DATABASE_URL,
- connect_args={"check_same_thread": False}
+ SQLALCHEMY_DATABASE_URL,
+ connect_args={"check_same_thread": False} if SQLALCHEMY_DATABASE_URL.startswith("sqlite") else {}
)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| Base = declarative_base() | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def get_db(): | ||||||||||||||||||||||||||||||||||||||||||||
| db = SessionLocal() | ||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||
| yield db | ||||||||||||||||||||||||||||||||||||||||||||
| finally: | ||||||||||||||||||||||||||||||||||||||||||||
| db.close() | ||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,108 @@ | ||||||||||||||||||||||||||||||||||||||
| from fastapi import FastAPI, HTTPException, Depends | ||||||||||||||||||||||||||||||||||||||
| from fastapi.middleware.cors import CORSMiddleware | ||||||||||||||||||||||||||||||||||||||
| from sqlalchemy.orm import Session | ||||||||||||||||||||||||||||||||||||||
| from typing import List, Optional | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| from . import models, schemas, database | ||||||||||||||||||||||||||||||||||||||
| from .database import get_db | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # Create database tables | ||||||||||||||||||||||||||||||||||||||
| models.Base.metadata.create_all(bind=database.engine) | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+9
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace with proper migration tooling for production. Creating tables at module import time is acceptable for initial development but has significant limitations:
For production readiness, integrate Alembic to manage migrations with versioning and rollback capabilities. Would you like me to generate an Alembic setup with initial migration scripts? |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| app = FastAPI( | ||||||||||||||||||||||||||||||||||||||
| title="AI Book Reader API", | ||||||||||||||||||||||||||||||||||||||
| description="API for managing notes and bookmarks in the AI Book Reader", | ||||||||||||||||||||||||||||||||||||||
| version="1.0.0" | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # Add CORS middleware | ||||||||||||||||||||||||||||||||||||||
| app.add_middleware( | ||||||||||||||||||||||||||||||||||||||
| CORSMiddleware, | ||||||||||||||||||||||||||||||||||||||
| allow_origins=["http://localhost:3000", "https://*.vercel.app"], | ||||||||||||||||||||||||||||||||||||||
| allow_credentials=True, | ||||||||||||||||||||||||||||||||||||||
| allow_methods=["*"], | ||||||||||||||||||||||||||||||||||||||
| allow_headers=["*"], | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+19
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The backend CORS setup lists Useful? React with 👍 / 👎. |
||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+18
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix CORS configuration: wildcard origins incompatible with credentials. The current CORS configuration has a critical flaw: browsers reject wildcard origins ( Additionally, allowing all methods and headers ( Solution 1 (recommended): Use specific allowed origins - allow_origins=["http://localhost:3000", "https://*.vercel.app"],
+ allow_origins=[
+ "http://localhost:3000",
+ "https://your-app.vercel.app", # Replace with actual domain
+ ],
allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
+ allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
+ allow_headers=["Content-Type", "Authorization"],Solution 2: Dynamic origin validation (if multiple Vercel deployments needed) from fastapi.middleware.cors import CORSMiddleware
import re
def is_allowed_origin(origin: str) -> bool:
allowed_patterns = [
r"^http://localhost:3000$",
r"^https://.*\.vercel\.app$",
]
return any(re.match(pattern, origin) for pattern in allowed_patterns)
app.add_middleware(
CORSMiddleware,
allow_origin_regex=r"^(http://localhost:3000|https://.*\.vercel\.app)$",
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["Content-Type", "Authorization"],
)🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @app.get("/") | ||||||||||||||||||||||||||||||||||||||
| def read_root(): | ||||||||||||||||||||||||||||||||||||||
| return {"message": "AI Book Reader API", "version": "1.0.0"} | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @app.get("/health") | ||||||||||||||||||||||||||||||||||||||
| def health_check(): | ||||||||||||||||||||||||||||||||||||||
| return {"status": "healthy"} | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # Notes endpoints | ||||||||||||||||||||||||||||||||||||||
| @app.post("/notes/", response_model=schemas.Note) | ||||||||||||||||||||||||||||||||||||||
| def create_note(note: schemas.NoteCreate, db: Session = Depends(get_db)): | ||||||||||||||||||||||||||||||||||||||
| db_note = models.Note(**note.dict()) | ||||||||||||||||||||||||||||||||||||||
| db.add(db_note) | ||||||||||||||||||||||||||||||||||||||
| db.commit() | ||||||||||||||||||||||||||||||||||||||
| db.refresh(db_note) | ||||||||||||||||||||||||||||||||||||||
| return db_note | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+37
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace deprecated The Apply this diff: def create_note(note: schemas.NoteCreate, db: Session = Depends(get_db)):
- db_note = models.Note(**note.dict())
+ db_note = models.Note(**note.model_dump())
db.add(db_note)
db.commit()
db.refresh(db_note)📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.13.2)37-37: Do not perform function call (B008) 🤖 Prompt for AI AgentsAdd database error handling for constraint violations. Database operations can fail due to constraint violations (unique keys, foreign keys, check constraints). Without error handling, these will return 500 errors with raw SQL exceptions exposed to clients. Wrap database operations with exception handling: +from sqlalchemy.exc import IntegrityError
+
def create_note(note: schemas.NoteCreate, db: Session = Depends(get_db)):
- db_note = models.Note(**note.model_dump())
- db.add(db_note)
- db.commit()
- db.refresh(db_note)
- return db_note
+ try:
+ db_note = models.Note(**note.model_dump())
+ db.add(db_note)
+ db.commit()
+ db.refresh(db_note)
+ return db_note
+ except IntegrityError as e:
+ db.rollback()
+ raise HTTPException(status_code=400, detail="Database constraint violation")Apply similar error handling to all create, update, and delete operations.
🧰 Tools🪛 Ruff (0.13.2)37-37: Do not perform function call (B008) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @app.get("/notes/", response_model=List[schemas.Note]) | ||||||||||||||||||||||||||||||||||||||
| def read_notes(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): | ||||||||||||||||||||||||||||||||||||||
| notes = db.query(models.Note).offset(skip).limit(limit).all() | ||||||||||||||||||||||||||||||||||||||
| return notes | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @app.get("/notes/{note_id}", response_model=schemas.Note) | ||||||||||||||||||||||||||||||||||||||
| def read_note(note_id: int, db: Session = Depends(get_db)): | ||||||||||||||||||||||||||||||||||||||
| note = db.query(models.Note).filter(models.Note.id == note_id).first() | ||||||||||||||||||||||||||||||||||||||
| if note is None: | ||||||||||||||||||||||||||||||||||||||
| raise HTTPException(status_code=404, detail="Note not found") | ||||||||||||||||||||||||||||||||||||||
| return note | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @app.put("/notes/{note_id}", response_model=schemas.Note) | ||||||||||||||||||||||||||||||||||||||
| def update_note(note_id: int, note: schemas.NoteUpdate, db: Session = Depends(get_db)): | ||||||||||||||||||||||||||||||||||||||
| db_note = db.query(models.Note).filter(models.Note.id == note_id).first() | ||||||||||||||||||||||||||||||||||||||
| if db_note is None: | ||||||||||||||||||||||||||||||||||||||
| raise HTTPException(status_code=404, detail="Note not found") | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| for key, value in note.dict(exclude_unset=True).items(): | ||||||||||||||||||||||||||||||||||||||
| setattr(db_note, key, value) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| db.commit() | ||||||||||||||||||||||||||||||||||||||
| db.refresh(db_note) | ||||||||||||||||||||||||||||||||||||||
| return db_note | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @app.delete("/notes/{note_id}") | ||||||||||||||||||||||||||||||||||||||
| def delete_note(note_id: int, db: Session = Depends(get_db)): | ||||||||||||||||||||||||||||||||||||||
| db_note = db.query(models.Note).filter(models.Note.id == note_id).first() | ||||||||||||||||||||||||||||||||||||||
| if db_note is None: | ||||||||||||||||||||||||||||||||||||||
| raise HTTPException(status_code=404, detail="Note not found") | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| db.delete(db_note) | ||||||||||||||||||||||||||||||||||||||
| db.commit() | ||||||||||||||||||||||||||||||||||||||
| return {"message": "Note deleted"} | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # Bookmarks endpoints | ||||||||||||||||||||||||||||||||||||||
| @app.post("/bookmarks/", response_model=schemas.Bookmark) | ||||||||||||||||||||||||||||||||||||||
| def create_bookmark(bookmark: schemas.BookmarkCreate, db: Session = Depends(get_db)): | ||||||||||||||||||||||||||||||||||||||
| db_bookmark = models.Bookmark(**bookmark.dict()) | ||||||||||||||||||||||||||||||||||||||
| db.add(db_bookmark) | ||||||||||||||||||||||||||||||||||||||
| db.commit() | ||||||||||||||||||||||||||||||||||||||
| db.refresh(db_bookmark) | ||||||||||||||||||||||||||||||||||||||
| return db_bookmark | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+81
to
+86
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace deprecated Same issues as the notes endpoints: use Apply this diff: +from sqlalchemy.exc import IntegrityError
+
def create_bookmark(bookmark: schemas.BookmarkCreate, db: Session = Depends(get_db)):
- db_bookmark = models.Bookmark(**bookmark.dict())
- db.add(db_bookmark)
- db.commit()
- db.refresh(db_bookmark)
- return db_bookmark
+ try:
+ db_bookmark = models.Bookmark(**bookmark.model_dump())
+ db.add(db_bookmark)
+ db.commit()
+ db.refresh(db_bookmark)
+ return db_bookmark
+ except IntegrityError as e:
+ db.rollback()
+ raise HTTPException(status_code=400, detail="Database constraint violation")📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.13.2)81-81: Do not perform function call (B008) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @app.get("/bookmarks/", response_model=List[schemas.Bookmark]) | ||||||||||||||||||||||||||||||||||||||
| def read_bookmarks(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): | ||||||||||||||||||||||||||||||||||||||
| bookmarks = db.query(models.Bookmark).offset(skip).limit(limit).all() | ||||||||||||||||||||||||||||||||||||||
| return bookmarks | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @app.get("/bookmarks/{bookmark_id}", response_model=schemas.Bookmark) | ||||||||||||||||||||||||||||||||||||||
| def read_bookmark(bookmark_id: int, db: Session = Depends(get_db)): | ||||||||||||||||||||||||||||||||||||||
| bookmark = db.query(models.Bookmark).filter(models.Bookmark.id == bookmark_id).first() | ||||||||||||||||||||||||||||||||||||||
| if bookmark is None: | ||||||||||||||||||||||||||||||||||||||
| raise HTTPException(status_code=404, detail="Bookmark not found") | ||||||||||||||||||||||||||||||||||||||
| return bookmark | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @app.delete("/bookmarks/{bookmark_id}") | ||||||||||||||||||||||||||||||||||||||
| def delete_bookmark(bookmark_id: int, db: Session = Depends(get_db)): | ||||||||||||||||||||||||||||||||||||||
| db_bookmark = db.query(models.Bookmark).filter(models.Bookmark.id == bookmark_id).first() | ||||||||||||||||||||||||||||||||||||||
| if db_bookmark is None: | ||||||||||||||||||||||||||||||||||||||
| raise HTTPException(status_code=404, detail="Bookmark not found") | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| db.delete(db_bookmark) | ||||||||||||||||||||||||||||||||||||||
| db.commit() | ||||||||||||||||||||||||||||||||||||||
| return {"message": "Bookmark deleted"} | ||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,27 @@ | ||||||||||
| from sqlalchemy import Column, Integer, String, Text, DateTime, Boolean | ||||||||||
| from sqlalchemy.sql import func | ||||||||||
| from .database import Base | ||||||||||
|
|
||||||||||
| class Note(Base): | ||||||||||
| __tablename__ = "notes" | ||||||||||
|
|
||||||||||
| id = Column(Integer, primary_key=True, index=True) | ||||||||||
| title = Column(String(255), nullable=False, index=True) | ||||||||||
| content = Column(Text, nullable=False) | ||||||||||
| book_title = Column(String(255), nullable=False) | ||||||||||
| page_number = Column(Integer, nullable=True) | ||||||||||
| chapter = Column(String(255), nullable=True) | ||||||||||
| created_at = Column(DateTime(timezone=True), server_default=func.now()) | ||||||||||
| updated_at = Column(DateTime(timezone=True), onupdate=func.now()) | ||||||||||
|
Comment on lines
+14
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix The Apply this diff to fix the issue: - updated_at = Column(DateTime(timezone=True), onupdate=func.now())
+ updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| class Bookmark(Base): | ||||||||||
| __tablename__ = "bookmarks" | ||||||||||
|
|
||||||||||
| id = Column(Integer, primary_key=True, index=True) | ||||||||||
| book_title = Column(String(255), nullable=False, index=True) | ||||||||||
| page_number = Column(Integer, nullable=False) | ||||||||||
| chapter = Column(String(255), nullable=True) | ||||||||||
| quote = Column(Text, nullable=True) | ||||||||||
| notes = Column(Text, nullable=True) | ||||||||||
| is_favorite = Column(Boolean, default=False) | ||||||||||
| created_at = Column(DateTime(timezone=True), server_default=func.now()) | ||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Verify Python version requirement.
The README specifies Python 3.12+ as a prerequisite. Ensure this matches the actual Python version used in development and any version constraints in the backend code or deployment configuration.
🏁 Script executed:
Length of output: 32
🏁 Script executed:
Length of output: 68
Enforce or align the Python 3.12+ requirement
No Python version constraint is declared in your configs. Add a
pythonVersion: "3.12"entry under the Python builder in vercel.json (or include a runtime.txt/CI setting) to enforce 3.12+, or adjust the README to reflect the actual supported version.🤖 Prompt for AI Agents