Skip to content

Commit 7fc1ad8

Browse files
committed
feat: implement pdfGUI React + FastAPI migration
Backend (FastAPI): - Core configuration and database setup - SQLAlchemy models for all 17 tables (users, projects, fittings, phases, atoms, datasets, calculations, parameters, constraints, files, history) - Pydantic schemas for request/response validation - Service layer wrapping original pdfGUI logic unchanged: - FittingService (refinement workflows) - StructureService (crystal structure operations) - DatasetService (PDF data handling) - ConstraintService (formula parsing/evaluation) - FileService (upload/parsing) - AuthService (JWT + bcrypt authentication) - REST API endpoints for all CRUD operations - Main FastAPI application with CORS Frontend (React): - TypeScript types for JSON-driven forms and charts - DynamicForm component with Zod validation - Wizard component for multi-step workflows - PDFPlot component using Plotly - JSON form schemas (fitting wizard) - Chart template configurations - API client service with auth interceptors - Zustand auth store with persist - React Router setup with protected routes Tests: - Pytest configuration with fixtures - Numerical regression tests matching original pdfGUI: - Grid interpolation (15 decimal precision) - Constraint evaluation - Structure operations - Dataset operations - Project loading - API endpoint tests Documentation: - Complete requirements gap analysis (10-pass review) - 70% overall completion with clear path to 100%
1 parent 038db8f commit 7fc1ad8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+6522
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
"""pdfGUI Migration Backend - FastAPI Application"""
2+
__version__ = "1.0.0"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""API router configuration."""
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""API dependencies."""
2+
from fastapi import Depends, HTTPException, status
3+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
4+
from sqlalchemy.orm import Session
5+
from ..core.database import get_db
6+
from ..core.security import decode_token
7+
from ..models.user import User
8+
from uuid import UUID
9+
10+
security = HTTPBearer()
11+
12+
13+
def get_current_user(
14+
credentials: HTTPAuthorizationCredentials = Depends(security),
15+
db: Session = Depends(get_db)
16+
) -> User:
17+
"""Get current authenticated user from JWT token."""
18+
token = credentials.credentials
19+
20+
payload = decode_token(token)
21+
if not payload:
22+
raise HTTPException(
23+
status_code=status.HTTP_401_UNAUTHORIZED,
24+
detail="Invalid or expired token"
25+
)
26+
27+
if payload.get("type") != "access":
28+
raise HTTPException(
29+
status_code=status.HTTP_401_UNAUTHORIZED,
30+
detail="Invalid token type"
31+
)
32+
33+
user_id = payload.get("sub")
34+
if not user_id:
35+
raise HTTPException(
36+
status_code=status.HTTP_401_UNAUTHORIZED,
37+
detail="Invalid token payload"
38+
)
39+
40+
user = db.query(User).filter(User.id == UUID(user_id)).first()
41+
if not user:
42+
raise HTTPException(
43+
status_code=status.HTTP_401_UNAUTHORIZED,
44+
detail="User not found"
45+
)
46+
47+
if not user.is_active:
48+
raise HTTPException(
49+
status_code=status.HTTP_403_FORBIDDEN,
50+
detail="User account is disabled"
51+
)
52+
53+
return user
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""API v1 router."""
2+
from fastapi import APIRouter
3+
from .endpoints import auth, projects, fittings, phases, datasets, files
4+
5+
api_router = APIRouter()
6+
7+
api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
8+
api_router.include_router(projects.router, prefix="/projects", tags=["projects"])
9+
api_router.include_router(fittings.router, prefix="/fittings", tags=["fittings"])
10+
api_router.include_router(phases.router, prefix="/phases", tags=["phases"])
11+
api_router.include_router(datasets.router, prefix="/datasets", tags=["datasets"])
12+
api_router.include_router(files.router, prefix="/files", tags=["files"])
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""API endpoints."""
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
"""Authentication endpoints."""
2+
from fastapi import APIRouter, Depends, HTTPException, status, Request
3+
from sqlalchemy.orm import Session
4+
from ....core.database import get_db
5+
from ....schemas.user import UserCreate, UserLogin, UserResponse, Token, TokenRefresh
6+
from ....services.auth_service import AuthService
7+
8+
router = APIRouter()
9+
10+
11+
@router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
12+
def register(user_data: UserCreate, db: Session = Depends(get_db)):
13+
"""Register a new user."""
14+
auth_service = AuthService(db)
15+
try:
16+
user = auth_service.create_user(
17+
email=user_data.email,
18+
password=user_data.password,
19+
first_name=user_data.first_name,
20+
last_name=user_data.last_name
21+
)
22+
return user
23+
except ValueError as e:
24+
raise HTTPException(status_code=400, detail=str(e))
25+
26+
27+
@router.post("/login", response_model=Token)
28+
def login(
29+
credentials: UserLogin,
30+
request: Request,
31+
db: Session = Depends(get_db)
32+
):
33+
"""Authenticate user and get tokens."""
34+
auth_service = AuthService(db)
35+
user = auth_service.authenticate(credentials.email, credentials.password)
36+
37+
if not user:
38+
raise HTTPException(
39+
status_code=status.HTTP_401_UNAUTHORIZED,
40+
detail="Invalid credentials"
41+
)
42+
43+
if not user.is_active:
44+
raise HTTPException(
45+
status_code=status.HTTP_403_FORBIDDEN,
46+
detail="User account is disabled"
47+
)
48+
49+
tokens = auth_service.create_session(
50+
user,
51+
ip_address=request.client.host if request.client else None,
52+
user_agent=request.headers.get("user-agent")
53+
)
54+
55+
return tokens
56+
57+
58+
@router.post("/refresh", response_model=Token)
59+
def refresh_token(token_data: TokenRefresh, db: Session = Depends(get_db)):
60+
"""Refresh access token."""
61+
auth_service = AuthService(db)
62+
tokens = auth_service.refresh_access_token(token_data.refresh_token)
63+
64+
if not tokens:
65+
raise HTTPException(
66+
status_code=status.HTTP_401_UNAUTHORIZED,
67+
detail="Invalid refresh token"
68+
)
69+
70+
return tokens
71+
72+
73+
@router.post("/logout")
74+
def logout(token_data: TokenRefresh, db: Session = Depends(get_db)):
75+
"""Logout and invalidate session."""
76+
auth_service = AuthService(db)
77+
success = auth_service.logout(token_data.refresh_token)
78+
79+
return {"success": success}

0 commit comments

Comments
 (0)