Skip to content

Commit 2389a2b

Browse files
committed
included frontend code
2 parents 6a6c7d5 + eb56fd6 commit 2389a2b

39 files changed

+4107
-87
lines changed

backend/.pre-commit-config.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
repos:
2+
- repo: https://github.com/psf/black
3+
rev: 24.8.0
4+
hooks:
5+
- id: black
6+
args: [--line-length=100, backend]
7+
8+
- repo: https://github.com/astral-sh/ruff-pre-commit
9+
rev: v0.6.9
10+
hooks:
11+
- id: ruff
12+
args: [--fix, backend]

backend/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010
8. Set the values for the variables ```MONGODB_URL``` (should be the connection string with <db_password> replaced by the password that Sierra will give you) and ```ACCESS_TOKEN_EXPIRE_MINUTES``` (300 is fine for now)
1111
9. Run ```python run.py```
1212
10. Install Postman
13-
11. Make a new tab, and send a GET request to ```http://localhost:8080/health``` and ensure that the returned result is ```{
14-
"status": "ok",
15-
"message": "API is running"
16-
}```
13+
11. Make a new tab, and send a GET request to ```http://localhost:8080/health``` and ensure that the returned result is ```{ "status": "ok", "message": "API is running" }```
1714
12. Make a MongoDB account and get Sierra to add you to the list of users by providing you an email
15+
13. Run ```pre-commit install```
1816

1917
# Software Bootcamp Onboarding
2018

2119
1. Checkout the API endpoints in the ```persons``` controller using Postman
2220
2. Hit the ```/new``` endpoint to add yourself to the DB!
23-
3. Sign into your MongoDB account or connect to MongoDB via VSCode in the browser and verify that your name appears in the collection!
21+
3. Sign into your MongoDB account in the browser and verify that your name appears in the collection!
22+
4. Get MongoDB VSCode extension
23+
5. Make a new branch using git and make a new endpoint to fetch yourself

backend/app/api/endpoints/health.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
router = APIRouter()
44

5+
56
@router.get("/health", status_code=200)
67
async def health_check():
78
"""Health check endpoint to verify API status"""
Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
1-
from fastapi import APIRouter, Body
2-
from typing import List
3-
from app.schemas.person import Person, CreatePersonRequest
1+
from typing import Annotated
2+
43
from app.models.person import person_model
4+
from app.schemas.person import CreatePersonRequest, Person
5+
from fastapi import APIRouter, Body
56

67
router = APIRouter()
78

9+
810
@router.post("/new", response_model=Person)
9-
async def create_person(person: CreatePersonRequest = Body(...)):
11+
async def create_person(person: Annotated[CreatePersonRequest, Body(...)]) -> Person:
1012
return await person_model.create_person(person)
1113

12-
@router.get("/all", response_model=List[Person])
13-
async def get_persons():
14+
15+
@router.get("/all", response_model=list[Person])
16+
async def get_persons() -> list[Person]:
1417
return await person_model.get_all_persons()
1518

19+
1620
@router.delete("/clear", response_model=None)
17-
async def clear_persons():
18-
return await person_model.delete_all_persons()
21+
async def clear_persons() -> None:
22+
return await person_model.delete_all_persons()

backend/app/api/endpoints/users.py

Lines changed: 42 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
from fastapi import APIRouter, HTTPException, Depends, status, UploadFile, File, Form, Body
2-
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
3-
from jose import JWTError, jwt
4-
from typing import List, Optional, Tuple
5-
from datetime import timedelta, datetime
6-
from bson import ObjectId
7-
from pydantic import EmailStr, ValidationError
8-
from app.schemas.user import User, UserCreate, UserResponse, UserInDB
9-
from app.models.user import user_model
10-
from app.utils import hash_password, verify_password, create_access_token, settings
111
import logging
122
import re
13-
import json
14-
from fastapi.responses import JSONResponse
3+
from datetime import timedelta
4+
from typing import Annotated
5+
6+
from app.models.user import user_model
7+
from app.schemas.user import User, UserInDB, UserResponse
8+
from app.utils import create_access_token, hash_password, settings, verify_password
9+
from bson import ObjectId
10+
from fastapi import APIRouter, Depends, Form, HTTPException, status
11+
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
12+
from jose import JWTError, jwt
13+
from pydantic import EmailStr
14+
1515
router = APIRouter()
1616

1717

@@ -22,7 +22,8 @@
2222
# OAuth2 scheme
2323
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
2424

25-
def validate_password(password: str) -> Tuple[bool, str]:
25+
26+
def validate_password(password: str) -> tuple[bool, str]:
2627
"""
2728
Validate password strength
2829
Returns: (is_valid: bool, message: str)
@@ -39,12 +40,13 @@ def validate_password(password: str) -> Tuple[bool, str]:
3940
return False, "Password must contain at least one special character"
4041
return True, "Password is valid"
4142

42-
def validate_email(email: str) -> Tuple[bool, str]:
43+
44+
def validate_email(email: str) -> tuple[bool, str]:
4345
"""
4446
Validate email format and domain
4547
Returns: (is_valid: bool, message: str)
4648
"""
47-
email_regex = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
49+
email_regex = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
4850
if not re.match(email_regex, email):
4951
return False, "Invalid email format"
5052
# Add additional domain validation if needed
@@ -53,26 +55,25 @@ def validate_email(email: str) -> Tuple[bool, str]:
5355

5456
@router.post("/", response_model=UserResponse)
5557
async def create_user(
56-
username: str = Form(...),
57-
email: EmailStr = Form(...),
58-
password: str = Form(...),
59-
first_name: Optional[str] = Form(None),
60-
last_name: Optional[str] = Form(None)
58+
username: Annotated[str, Form(...)],
59+
email: Annotated[EmailStr, Form(...)],
60+
password: Annotated[str, Form(...)],
61+
first_name: Annotated[str | None, Form()] = None,
62+
last_name: Annotated[str | None, Form()] = None,
6163
):
6264
"""
6365
Create a new user with publications and avatar image.
6466
"""
6567
logger.info(f"Creating new user with email: {email}")
6668

6769
# Check if user exists
68-
user_exists = await user_model.check_existing_username_and_email(username.lower(), email.lower())
70+
user_exists = await user_model.check_existing_username_and_email(
71+
username.lower(), email.lower()
72+
)
6973

7074
if user_exists:
7175
detail = "Email or username already registered"
72-
raise HTTPException(
73-
status_code=status.HTTP_400_BAD_REQUEST,
74-
detail=detail
75-
)
76+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=detail)
7677

7778
# Create user document
7879
user_id = str(ObjectId())
@@ -94,15 +95,13 @@ async def create_user(
9495
except Exception as e:
9596
logger.error(f"Database error during user creation: {e}")
9697
raise HTTPException(
97-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
98-
detail="Error creating user account"
99-
)
98+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error creating user account"
99+
) from None
100100

101101
# Create access token
102102
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
103103
access_token = create_access_token(
104-
data={"sub": email.lower()},
105-
expires_delta=access_token_expires
104+
data={"sub": email.lower()}, expires_delta=access_token_expires
106105
)
107106

108107
# Prepare user response
@@ -115,15 +114,16 @@ async def create_user(
115114
username=username.lower(),
116115
first_name=first_name,
117116
last_name=last_name,
118-
)
117+
),
119118
)
120119

121120
logger.info(f"Successfully created user with email: {email}")
122121
return user_response
123122

123+
124124
# Update the login endpoint
125125
@router.post("/token", response_model=UserResponse)
126-
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
126+
async def login_for_access_token(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
127127
logger.info(f"Login attempt for user: {form_data.username}")
128128

129129
# Find user by username (case insensitive)
@@ -150,8 +150,7 @@ async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(
150150
# Create access token
151151
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
152152
access_token = create_access_token(
153-
data={"sub": user["email"]},
154-
expires_delta=access_token_expires
153+
data={"sub": user["email"]}, expires_delta=access_token_expires
155154
)
156155

157156
# Prepare user response
@@ -163,13 +162,14 @@ async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(
163162
email=user["email"],
164163
username=user["username"],
165164
first_name=user.get("first_name"),
166-
last_name=user.get("last_name")
167-
)
165+
last_name=user.get("last_name"),
166+
),
168167
)
169168

170169
logger.info(f"Login successful for user: {form_data.username}")
171170
return user_response
172171

172+
173173
# Get current user
174174
async def get_current_user(token: str = Depends(oauth2_scheme)):
175175
credentials_exception = HTTPException(
@@ -183,23 +183,26 @@ async def get_current_user(token: str = Depends(oauth2_scheme)):
183183
if email is None:
184184
raise credentials_exception
185185
except JWTError:
186-
raise credentials_exception
186+
raise credentials_exception from None
187187
user = await user_model.get_by_email(email)
188188
if user is None:
189189
raise credentials_exception
190190
return UserInDB(**user)
191191

192+
192193
# Protected route to get current user info
193194
@router.get("/me", response_model=User)
194-
async def read_users_me(current_user: User = Depends(get_current_user)):
195+
async def read_users_me(current_user: Annotated[User, Depends(get_current_user)]):
195196
return current_user
196197

197-
@router.get("/all", response_model=List[User])
198+
199+
@router.get("/all", response_model=list[User])
198200
async def get_all_users():
199201
"""get all users"""
200202
users = await user_model.get_all()
201203
return users
202204

205+
203206
@router.get("/{user_id}", response_model=User)
204207
async def get_user(user_id: str):
205208
"""get user by id"""

backend/app/core/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from pydantic_settings import BaseSettings
22

3+
34
class Settings(BaseSettings):
45
PROJECT_NAME: str = "Person"
56
API_V1_STR: str = "/api/v1"
@@ -12,4 +13,5 @@ class Settings(BaseSettings):
1213
class Config:
1314
env_file = ".env"
1415

16+
1517
settings = Settings()

backend/app/database/mongodb.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from motor.motor_asyncio import AsyncIOMotorClient
2+
23
from app.core.config import settings
34

45
client = AsyncIOMotorClient(settings.MONGODB_URL)

backend/app/main.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
# main.py
2-
from fastapi import FastAPI, Depends, HTTPException, status
3-
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
4-
from app.api.endpoints import users, health
5-
from app.utils import verify_password, create_access_token
6-
from app.models.user import user_model
71
from datetime import timedelta
8-
from jose import JWTError, jwt
2+
from typing import Annotated
3+
4+
from fastapi import Depends, FastAPI, HTTPException, status
95
from fastapi.middleware.cors import CORSMiddleware
6+
from fastapi.security import OAuth2PasswordRequestForm
7+
8+
from app.api.endpoints import health, persons, users
109
from app.core.config import settings
11-
from app.api.endpoints import persons
10+
from app.models.user import user_model
11+
from app.utils import create_access_token, verify_password
1212

1313
app = FastAPI()
1414

@@ -35,9 +35,10 @@
3535

3636
app.include_router(health.router, prefix="", tags=["Health"])
3737

38+
3839
# Login
3940
@app.post("/token", response_model=dict)
40-
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
41+
async def login_for_access_token(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
4142
user = await user_model.get_by_email(form_data.username)
4243
if not user or not verify_password(form_data.password, user["hashed_password"]):
4344
raise HTTPException(

backend/app/models/person.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
from typing import List
2-
from motor.motor_asyncio import AsyncIOMotorCollection
3-
from app.schemas.person import Person, CreatePersonRequest
1+
from motor.motor_asyncio import AsyncIOMotorCollection # noqa: TCH002
2+
43
from app.database.mongodb import db
4+
from app.schemas.person import CreatePersonRequest, Person
5+
56

67
class PersonModel:
78
def __init__(self):
@@ -14,12 +15,13 @@ async def create_person(self, person: CreatePersonRequest) -> Person:
1415

1516
return Person(**person_data)
1617

17-
async def get_all_persons(self) -> List[Person]:
18+
async def get_all_persons(self) -> list[Person]:
1819
persons_list = await self.collection.find().to_list(length=None)
1920

2021
return [Person(**person) for person in persons_list]
2122

2223
async def delete_all_persons(self) -> None:
2324
await self.collection.delete_many({})
2425

25-
person_model = PersonModel()
26+
27+
person_model = PersonModel()

backend/app/models/user.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# app/models/user.py
21
from pydantic import EmailStr
3-
from app.schemas.user import UserInDB
2+
43
from app.database.mongodb import db
5-
from typing import List, Tuple
4+
from app.schemas.user import UserInDB
5+
66

77
class UserModel:
88
def __init__(self):
@@ -16,22 +16,19 @@ async def get_by_username(self, username: str) -> UserInDB:
1616

1717
async def get_by_id(self, id: str) -> UserInDB:
1818
return await self.collection.find_one({"id": id})
19-
19+
2020
async def get_all(self):
2121
return await self.collection.find().to_list(1000)
22-
22+
2323
async def check_existing_username_and_email(self, username: str, email: EmailStr) -> bool:
24-
existing_user = await self.collection.find_one({
25-
"$or": [
26-
{"email": email.lower()},
27-
{"username": username.lower()}
28-
]
29-
})
24+
existing_user = await self.collection.find_one(
25+
{"$or": [{"email": email.lower()}, {"username": username.lower()}]}
26+
)
3027

3128
return existing_user is not None
3229

3330
async def create_user(self, form_data):
3431
return await self.collection.insert_one(form_data)
3532

3633

37-
user_model = UserModel()
34+
user_model = UserModel()

0 commit comments

Comments
 (0)