Skip to content

Commit 31a4254

Browse files
Partial refactor
1 parent 094e92a commit 31a4254

File tree

14 files changed

+246
-201
lines changed

14 files changed

+246
-201
lines changed

main.py

Lines changed: 28 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,16 @@
55
from fastapi.responses import RedirectResponse
66
from fastapi.staticfiles import StaticFiles
77
from fastapi.templating import Jinja2Templates
8-
from fastapi.exceptions import RequestValidationError, HTTPException, StarletteHTTPException
9-
from sqlmodel import Session
10-
from routers import authentication, organization, role, user
8+
from fastapi.exceptions import RequestValidationError, StarletteHTTPException
9+
from routers import authentication, organization, role, user, dashboard, terms_of_service, privacy_policy, about
1110
from utils.auth import (
12-
HTML_PASSWORD_PATTERN,
13-
get_user_with_relations,
14-
get_optional_user,
1511
NeedsNewTokens,
16-
get_user_from_reset_token,
1712
PasswordValidationError,
18-
AuthenticationError
13+
AuthenticationError,
14+
get_optional_user
1915
)
16+
from utils.db import set_up_db
2017
from utils.models import User
21-
from utils.db import get_session, set_up_db
22-
from utils.images import MAX_FILE_SIZE, MIN_DIMENSION, MAX_DIMENSION, ALLOWED_CONTENT_TYPES
2318

2419
logger = logging.getLogger("uvicorn.error")
2520
logger.setLevel(logging.DEBUG)
@@ -33,23 +28,35 @@ async def lifespan(app: FastAPI):
3328
# Optional shutdown logic
3429

3530

31+
# Initialize the FastAPI app
3632
app: FastAPI = FastAPI(lifespan=lifespan)
3733

38-
# Mount static files (e.g., CSS, JS)
34+
# Mount static files (e.g., CSS, JS) and initialize Jinja2 templates
3935
app.mount("/static", StaticFiles(directory="static"), name="static")
40-
41-
# Initialize Jinja2 templates
4236
templates = Jinja2Templates(directory="templates")
4337

4438

39+
# --- Include Routers ---
40+
41+
42+
app.include_router(authentication.router)
43+
app.include_router(organization.router)
44+
app.include_router(role.router)
45+
app.include_router(user.router)
46+
app.include_router(dashboard.router)
47+
app.include_router(terms_of_service.router)
48+
app.include_router(privacy_policy.router)
49+
app.include_router(about.router)
50+
51+
4552
# --- Exception Handling Middlewares ---
4653

4754

4855
# Handle AuthenticationError by redirecting to login page
4956
@app.exception_handler(AuthenticationError)
5057
async def authentication_error_handler(request: Request, exc: AuthenticationError):
5158
return RedirectResponse(
52-
url="/login",
59+
url=app.url_path_for("read_login"),
5360
status_code=status.HTTP_303_SEE_OTHER
5461
)
5562

@@ -146,160 +153,21 @@ async def general_exception_handler(request: Request, exc: Exception):
146153
)
147154

148155

149-
# --- Unauthenticated Routes ---
150-
151-
152-
# Define a dependency for common parameters
153-
async def common_unauthenticated_parameters(
154-
request: Request,
155-
user: Optional[User] = Depends(get_optional_user),
156-
error_message: Optional[str] = None,
157-
) -> dict:
158-
return {"request": request, "user": user, "error_message": error_message}
156+
# --- Home Page ---
159157

160158

161159
@app.get("/")
162160
async def read_home(
163-
params: dict = Depends(common_unauthenticated_parameters)
164-
):
165-
if params["user"]:
166-
return RedirectResponse(url="/dashboard", status_code=302)
167-
return templates.TemplateResponse(params["request"], "index.html", params)
168-
169-
170-
@app.get("/login")
171-
async def read_login(
172-
params: dict = Depends(common_unauthenticated_parameters),
173-
email_updated: Optional[str] = "false"
174-
):
175-
if params["user"]:
176-
return RedirectResponse(url="/dashboard", status_code=302)
177-
params["email_updated"] = email_updated
178-
return templates.TemplateResponse(params["request"], "authentication/login.html", params)
179-
180-
181-
@app.get("/register")
182-
async def read_register(
183-
params: dict = Depends(common_unauthenticated_parameters)
184-
):
185-
if params["user"]:
186-
return RedirectResponse(url="/dashboard", status_code=302)
187-
188-
params["password_pattern"] = HTML_PASSWORD_PATTERN
189-
return templates.TemplateResponse(params["request"], "authentication/register.html", params)
190-
191-
192-
@app.get("/forgot_password")
193-
async def read_forgot_password(
194-
params: dict = Depends(common_unauthenticated_parameters),
195-
show_form: Optional[str] = "true",
196-
):
197-
params["show_form"] = show_form == "true"
198-
199-
return templates.TemplateResponse(params["request"], "authentication/forgot_password.html", params)
200-
201-
202-
@app.get("/about")
203-
async def read_about(params: dict = Depends(common_unauthenticated_parameters)):
204-
return templates.TemplateResponse(params["request"], "about.html", params)
205-
206-
207-
@app.get("/privacy_policy")
208-
async def read_privacy_policy(params: dict = Depends(common_unauthenticated_parameters)):
209-
return templates.TemplateResponse(params["request"], "privacy_policy.html", params)
210-
211-
212-
@app.get("/terms_of_service")
213-
async def read_terms_of_service(params: dict = Depends(common_unauthenticated_parameters)):
214-
return templates.TemplateResponse(params["request"], "terms_of_service.html", params)
215-
216-
217-
@app.get("/auth/reset_password")
218-
async def read_reset_password(
219-
email: str,
220-
token: str,
221-
params: dict = Depends(common_unauthenticated_parameters),
222-
session: Session = Depends(get_session)
223-
):
224-
authorized_user, _ = get_user_from_reset_token(email, token, session)
225-
226-
# Raise informative error to let user know the token is invalid and may have expired
227-
if not authorized_user:
228-
raise HTTPException(status_code=400, detail="Invalid or expired token")
229-
230-
params["email"] = email
231-
params["token"] = token
232-
params["password_pattern"] = HTML_PASSWORD_PATTERN
233-
234-
return templates.TemplateResponse(params["request"], "authentication/reset_password.html", params)
235-
236-
237-
# --- Authenticated Routes ---
238-
239-
240-
# Define a dependency for common parameters
241-
async def common_authenticated_parameters(
242161
request: Request,
243-
user: User = Depends(get_user_with_relations),
244-
error_message: Optional[str] = None
245-
) -> dict:
246-
return {"request": request, "user": user, "error_message": error_message}
247-
248-
249-
# Redirect to home if user is not authenticated
250-
@app.get("/dashboard")
251-
async def read_dashboard(
252-
params: dict = Depends(common_authenticated_parameters)
162+
user: Optional[User] = Depends(get_optional_user)
253163
):
254-
return templates.TemplateResponse(params["request"], "dashboard/index.html", params)
255-
256-
257-
@app.get("/profile")
258-
async def read_profile(
259-
params: dict = Depends(common_authenticated_parameters),
260-
email_update_requested: Optional[str] = "false",
261-
email_updated: Optional[str] = "false"
262-
):
263-
# Add image constraints to the template context
264-
params.update({
265-
"max_file_size_mb": MAX_FILE_SIZE / (1024 * 1024), # Convert bytes to MB
266-
"min_dimension": MIN_DIMENSION,
267-
"max_dimension": MAX_DIMENSION,
268-
"allowed_formats": list(ALLOWED_CONTENT_TYPES.keys()),
269-
"email_update_requested": email_update_requested,
270-
"email_updated": email_updated
271-
})
272-
return templates.TemplateResponse(params["request"], "users/profile.html", params)
273-
274-
275-
@app.get("/organizations/{org_id}")
276-
async def read_organization(
277-
org_id: int,
278-
params: dict = Depends(common_authenticated_parameters)
279-
):
280-
# Get the organization only if the user is a member of it
281-
org = next(
282-
(org for org in params["user"].organizations if org.id == org_id),
283-
None
164+
if user:
165+
return RedirectResponse(url="/dashboard", status_code=302)
166+
return templates.TemplateResponse(
167+
"index.html",
168+
{"request": request, "user": user}
284169
)
285-
if not org:
286-
raise organization.OrganizationNotFoundError()
287-
288-
# Eagerly load roles and users
289-
org.roles
290-
org.users
291-
params["organization"] = org
292-
293-
return templates.TemplateResponse(params["request"], "users/organization.html", params)
294-
295170

296-
# --- Include Routers ---
297-
298-
299-
app.include_router(authentication.router)
300-
app.include_router(organization.router)
301-
app.include_router(role.router)
302-
app.include_router(user.router)
303171

304172
if __name__ == "__main__":
305173
import uvicorn

routers/about.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from typing import Optional
2+
from fastapi import APIRouter, Depends, Request
3+
from fastapi.templating import Jinja2Templates
4+
from utils.auth import get_optional_user
5+
from utils.models import User
6+
7+
router = APIRouter(prefix="/about", tags=["about"])
8+
templates = Jinja2Templates(directory="templates")
9+
10+
@router.get("/")
11+
async def read_about(
12+
request: Request,
13+
user: Optional[User] = Depends(get_optional_user)
14+
):
15+
return templates.TemplateResponse(
16+
"about.html",
17+
{"request": request, "user": user}
18+
)

routers/authentication.py

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
from datetime import datetime
66
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks, Form, Request
77
from fastapi.responses import RedirectResponse
8+
from fastapi.templating import Jinja2Templates
89
from pydantic import BaseModel, EmailStr, ConfigDict
910
from sqlmodel import Session, select
11+
from utils.db import get_session
1012
from utils.models import User, UserPassword, DataIntegrityError
1113
from utils.auth import (
12-
get_session,
14+
HTML_PASSWORD_PATTERN,
1315
get_user_from_reset_token,
1416
create_password_validator,
1517
create_passwords_match_validator,
@@ -22,12 +24,14 @@
2224
send_reset_email,
2325
send_email_update_confirmation,
2426
get_user_from_email_update_token,
25-
get_authenticated_user
27+
get_authenticated_user,
28+
get_optional_user
2629
)
2730

2831
logger = getLogger("uvicorn.error")
2932

3033
router = APIRouter(prefix="/auth", tags=["auth"])
34+
templates = Jinja2Templates(directory="templates")
3135

3236
# --- Custom Exceptions ---
3337

@@ -168,6 +172,69 @@ class UserRead(BaseModel):
168172
# --- Routes ---
169173

170174

175+
@router.get("/login")
176+
async def read_login(
177+
request: Request,
178+
user: Optional[User] = Depends(get_optional_user),
179+
email_updated: Optional[str] = "false"
180+
):
181+
if user:
182+
return RedirectResponse(url="/dashboard", status_code=302)
183+
return templates.TemplateResponse(
184+
"authentication/login.html",
185+
{"request": request, "user": user, "email_updated": email_updated}
186+
)
187+
188+
189+
@router.get("/register")
190+
async def read_register(
191+
request: Request,
192+
user: Optional[User] = Depends(get_optional_user)
193+
):
194+
if user:
195+
return RedirectResponse(url="/dashboard", status_code=302)
196+
197+
return templates.TemplateResponse(
198+
"authentication/register.html",
199+
{"request": request, "user": user}
200+
)
201+
202+
203+
@router.get("/forgot_password")
204+
async def read_forgot_password(
205+
request: Request,
206+
user: Optional[User] = Depends(get_optional_user),
207+
show_form: Optional[str] = "true",
208+
):
209+
if user:
210+
return RedirectResponse(url="/dashboard", status_code=302)
211+
212+
return templates.TemplateResponse(
213+
"authentication/forgot_password.html",
214+
{"request": request, "user": user, "show_form": show_form == "true"}
215+
)
216+
217+
218+
@router.get("/reset_password")
219+
async def read_reset_password(
220+
request: Request,
221+
email: str,
222+
token: str,
223+
user: Optional[User] = Depends(get_optional_user),
224+
session: Session = Depends(get_session)
225+
):
226+
authorized_user, _ = get_user_from_reset_token(email, token, session)
227+
228+
# Raise informative error to let user know the token is invalid and may have expired
229+
if not authorized_user:
230+
raise HTTPException(status_code=400, detail="Invalid or expired token")
231+
232+
return templates.TemplateResponse(
233+
"authentication/reset_password.html",
234+
{"request": request, "user": user, "email": email, "token": token, "password_pattern": HTML_PASSWORD_PATTERN}
235+
)
236+
237+
171238
# TODO: Use custom error message in the case where the user is already registered
172239
@router.post("/register", response_class=RedirectResponse)
173240
async def register(

routers/dashboard.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from typing import Optional
2+
from fastapi import APIRouter, Depends, Request
3+
from fastapi.templating import Jinja2Templates
4+
from utils.auth import get_user_with_relations
5+
from utils.models import User
6+
7+
router = APIRouter(prefix="/dashboard", tags=["dashboard"])
8+
templates = Jinja2Templates(directory="templates")
9+
10+
11+
# --- Authenticated Routes ---
12+
13+
14+
@router.get("/")
15+
async def read_dashboard(
16+
request: Request,
17+
user: Optional[User] = Depends(get_user_with_relations)
18+
):
19+
return templates.TemplateResponse(
20+
"dashboard/index.html",
21+
{"request": request, "user": user}
22+
)

0 commit comments

Comments
 (0)