Skip to content

Commit 6d9bc19

Browse files
committed
backend - geospatial routes, initial table, front end fucked
1 parent 7fddea8 commit 6d9bc19

36 files changed

+1160
-252
lines changed

.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,4 @@ SENTRY_DSN=
3434

3535
# Configure these with your own Docker registry images
3636
DOCKER_IMAGE_BACKEND=backend
37-
DOCKER_IMAGE_FRONTEND=frontend
37+
DOCKER_IMAGE_FRONTEND=frontend

backend/app/api/main.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1+
# This script sets up the main API router for the FastAPI application, organizing and including various sub-routers to handle specific sections of the API. Each sub-router, defined in separate route modules (items, login, users, utils, and geospatial), corresponds to different functional areas of the application, such as user management, item handling, utility functions, and geospatial data handling. By consolidating these routers under the main api_router, this script provides a centralized routing structure, enabling modular API section management and streamlined route access for the application’s endpoints.
2+
3+
# Import APIRouter from FastAPI and the other route modules
14
from fastapi import APIRouter
25

3-
from app.api.routes import items, login, users, utils
6+
# Import the other route modules
7+
from app.api.routes import items, login, users, utils, geospatial
48

9+
# Initialize the main API router
510
api_router = APIRouter()
11+
12+
# Include routers for different API sections
613
api_router.include_router(login.router, tags=["login"])
714
api_router.include_router(users.router, prefix="/users", tags=["users"])
815
api_router.include_router(utils.router, prefix="/utils", tags=["utils"])
916
api_router.include_router(items.router, prefix="/items", tags=["items"])
17+
api_router.include_router(geospatial.router, prefix="/geospatial", tags=["geospatial"])
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from fastapi import APIRouter, Depends, File, UploadFile, HTTPException
2+
from sqlalchemy.orm import Session
3+
from typing import List
4+
from uuid import UUID
5+
import os # Import os to handle file path and directory creation
6+
7+
from app import crud
8+
from app.models import GeoFile
9+
from app.api import deps
10+
from app.models import User
11+
12+
router = APIRouter()
13+
14+
# Dependency to get the current authenticated user
15+
# This uses the get_current_user function from deps.py
16+
# that decodes the JWT token, fetches the user from the database,
17+
# and ensures the user is active.
18+
@router.post("/upload", response_model=GeoFile)
19+
def upload_geofile(
20+
file: UploadFile = File(...), # Accepts an uploaded file
21+
description: str = None, # Optional description for the file
22+
db: Session = Depends(deps.get_db), # Database session dependency
23+
current_user: User = Depends(deps.get_current_user), # Get the current authenticated user
24+
):
25+
"""
26+
Endpoint to upload a geospatial file. The file is saved to a designated directory,
27+
and a record is created in the database associating the file with the current user.
28+
"""
29+
# Directory where files will be stored
30+
upload_dir = "uploads"
31+
os.makedirs(upload_dir, exist_ok=True) # Create directory if it doesn't exist
32+
33+
# Construct file path
34+
file_location = os.path.join(upload_dir, file.filename)
35+
36+
# Write the file to the storage path
37+
with open(file_location, "wb+") as file_object:
38+
file_object.write(file.file.read())
39+
40+
# Create geofile record in the database with user ownership
41+
geofile = crud.create_geofile(
42+
db=db,
43+
owner_id=current_user.id,
44+
file_name=file.filename,
45+
file_path=file_location,
46+
description=description,
47+
)
48+
return geofile # Return the created geofile record
49+
50+
51+
# Dependency to get a list of geofiles uploaded by the current user
52+
@router.get("/", response_model=List[GeoFile])
53+
def list_geofiles(
54+
db: Session = Depends(deps.get_db), # Database session dependency
55+
current_user: User = Depends(deps.get_current_user), # Get the current authenticated user
56+
):
57+
"""
58+
Retrieve a list of geofiles uploaded by the current user.
59+
"""
60+
return crud.get_user_geofiles(db=db, user_id=current_user.id)
61+
62+
63+
# Dependency to retrieve details of a specific geofile by its ID.
64+
# Access to geofile is restricted to the file's owner.
65+
@router.get("/{geofile_id}", response_model=GeoFile)
66+
def get_geofile(
67+
geofile_id: UUID, # Unique identifier for the geofile
68+
db: Session = Depends(deps.get_db), # Database session dependency
69+
current_user: User = Depends(deps.get_current_user), # Get the current authenticated user
70+
):
71+
"""
72+
Retrieve details of a specific geofile by its ID. Access is restricted to the file's owner.
73+
"""
74+
geofile = crud.get_geofile(db=db, geofile_id=geofile_id)
75+
76+
# Check if geofile exists and if the user is the owner
77+
if not geofile or geofile.owner_id != current_user.id:
78+
raise HTTPException(status_code=404, detail="Geofile not found")
79+
return geofile # Return geofile details if access is allowed

backend/app/api/routes/items.py

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,31 @@
11
import uuid
22
from typing import Any
3-
43
from fastapi import APIRouter, HTTPException
54
from sqlmodel import func, select
5+
from app.api.deps import CurrentUser, SessionDep # Custom dependencies for user and session
6+
from app.models import Item, ItemCreate, ItemPublic, ItemsPublic, ItemUpdate, Message # Importing models
67

7-
from app.api.deps import CurrentUser, SessionDep
8-
from app.models import Item, ItemCreate, ItemPublic, ItemsPublic, ItemUpdate, Message
9-
10-
router = APIRouter()
8+
router = APIRouter() # Create an APIRouter instance for routing
119

1210

1311
@router.get("/", response_model=ItemsPublic)
1412
def read_items(
1513
session: SessionDep, current_user: CurrentUser, skip: int = 0, limit: int = 100
1614
) -> Any:
1715
"""
18-
Retrieve items.
16+
Retrieve items for the logged-in user. If the user is a superuser, they can see all items.
17+
Otherwise, they can only see their own items.
1918
"""
20-
19+
# Check if the current user is a superuser
2120
if current_user.is_superuser:
21+
# Count all items in the database
2222
count_statement = select(func.count()).select_from(Item)
2323
count = session.exec(count_statement).one()
24+
# Retrieve items with pagination
2425
statement = select(Item).offset(skip).limit(limit)
2526
items = session.exec(statement).all()
2627
else:
28+
# For non-superusers, only retrieve items owned by the logged-in user
2729
count_statement = (
2830
select(func.count())
2931
.select_from(Item)
@@ -38,19 +40,19 @@ def read_items(
3840
)
3941
items = session.exec(statement).all()
4042

41-
return ItemsPublic(data=items, count=count)
43+
return ItemsPublic(data=items, count=count) # Return items and total count
4244

4345

4446
@router.get("/{id}", response_model=ItemPublic)
4547
def read_item(session: SessionDep, current_user: CurrentUser, id: uuid.UUID) -> Any:
4648
"""
47-
Get item by ID.
49+
Get a single item by ID. Users can only access their own items unless they are a superuser.
4850
"""
49-
item = session.get(Item, id)
51+
item = session.get(Item, id) # Retrieve item by ID
5052
if not item:
51-
raise HTTPException(status_code=404, detail="Item not found")
53+
raise HTTPException(status_code=404, detail="Item not found") # Item not found
5254
if not current_user.is_superuser and (item.owner_id != current_user.id):
53-
raise HTTPException(status_code=400, detail="Not enough permissions")
55+
raise HTTPException(status_code=400, detail="Not enough permissions") # Permission check
5456
return item
5557

5658

@@ -59,13 +61,14 @@ def create_item(
5961
*, session: SessionDep, current_user: CurrentUser, item_in: ItemCreate
6062
) -> Any:
6163
"""
62-
Create new item.
64+
Create a new item. The item will be associated with the logged-in user.
6365
"""
66+
# Create a new Item object, validating and assigning the owner (current user)
6467
item = Item.model_validate(item_in, update={"owner_id": current_user.id})
65-
session.add(item)
66-
session.commit()
67-
session.refresh(item)
68-
return item
68+
session.add(item) # Add the new item to the session
69+
session.commit() # Commit the transaction to the database
70+
session.refresh(item) # Refresh the item object to get the updated values
71+
return item # Return the newly created item
6972

7073

7174
@router.put("/{id}", response_model=ItemPublic)
@@ -77,33 +80,33 @@ def update_item(
7780
item_in: ItemUpdate,
7881
) -> Any:
7982
"""
80-
Update an item.
83+
Update an existing item. Users can only update their own items unless they are superusers.
8184
"""
82-
item = session.get(Item, id)
85+
item = session.get(Item, id) # Retrieve item by ID
8386
if not item:
84-
raise HTTPException(status_code=404, detail="Item not found")
87+
raise HTTPException(status_code=404, detail="Item not found") # Item not found
8588
if not current_user.is_superuser and (item.owner_id != current_user.id):
86-
raise HTTPException(status_code=400, detail="Not enough permissions")
87-
update_dict = item_in.model_dump(exclude_unset=True)
88-
item.sqlmodel_update(update_dict)
89-
session.add(item)
90-
session.commit()
91-
session.refresh(item)
92-
return item
89+
raise HTTPException(status_code=400, detail="Not enough permissions") # Permission check
90+
update_dict = item_in.model_dump(exclude_unset=True) # Prepare update data
91+
item.sqlmodel_update(update_dict) # Apply updates to the item
92+
session.add(item) # Add updated item to session
93+
session.commit() # Commit the transaction
94+
session.refresh(item) # Refresh the item object to get the updated values
95+
return item # Return the updated item
9396

9497

9598
@router.delete("/{id}")
9699
def delete_item(
97100
session: SessionDep, current_user: CurrentUser, id: uuid.UUID
98101
) -> Message:
99102
"""
100-
Delete an item.
103+
Delete an item. Users can only delete their own items unless they are superusers.
101104
"""
102-
item = session.get(Item, id)
105+
item = session.get(Item, id) # Retrieve item by ID
103106
if not item:
104-
raise HTTPException(status_code=404, detail="Item not found")
107+
raise HTTPException(status_code=404, detail="Item not found") # Item not found
105108
if not current_user.is_superuser and (item.owner_id != current_user.id):
106-
raise HTTPException(status_code=400, detail="Not enough permissions")
107-
session.delete(item)
108-
session.commit()
109-
return Message(message="Item deleted successfully")
109+
raise HTTPException(status_code=400, detail="Not enough permissions") # Permission check
110+
session.delete(item) # Delete the item from the session
111+
session.commit() # Commit the transaction
112+
return Message(message="Item deleted successfully") # Return a success message

backend/app/api/routes/login.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from fastapi.responses import HTMLResponse
66
from fastapi.security import OAuth2PasswordRequestForm
77

8+
# Import necessary modules and dependencies
89
from app import crud
910
from app.api.deps import CurrentUser, SessionDep, get_current_active_superuser
1011
from app.core import security
@@ -18,6 +19,7 @@
1819
verify_password_reset_token,
1920
)
2021

22+
# Initialize the APIRouter for defining login and password-related endpoints
2123
router = APIRouter()
2224

2325

@@ -26,7 +28,8 @@ def login_access_token(
2628
session: SessionDep, form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
2729
) -> Token:
2830
"""
29-
OAuth2 compatible token login, get an access token for future requests
31+
OAuth2 compatible token login, get an access token for future requests.
32+
Validates user credentials and returns a JWT access token if successful.
3033
"""
3134
user = crud.authenticate(
3235
session=session, email=form_data.username, password=form_data.password
@@ -46,15 +49,17 @@ def login_access_token(
4649
@router.post("/login/test-token", response_model=UserPublic)
4750
def test_token(current_user: CurrentUser) -> Any:
4851
"""
49-
Test access token
52+
Test access token.
53+
Validates the current access token and returns user details if valid.
5054
"""
5155
return current_user
5256

5357

5458
@router.post("/password-recovery/{email}")
5559
def recover_password(email: str, session: SessionDep) -> Message:
5660
"""
57-
Password Recovery
61+
Password recovery.
62+
Generates and sends a password reset token to the user's email if they exist in the system.
5863
"""
5964
user = crud.get_user_by_email(session=session, email=email)
6065

@@ -78,7 +83,8 @@ def recover_password(email: str, session: SessionDep) -> Message:
7883
@router.post("/reset-password/")
7984
def reset_password(session: SessionDep, body: NewPassword) -> Message:
8085
"""
81-
Reset password
86+
Reset password.
87+
Verifies the provided token, checks if the user exists and is active, then updates their password.
8288
"""
8389
email = verify_password_reset_token(token=body.token)
8490
if not email:
@@ -105,7 +111,8 @@ def reset_password(session: SessionDep, body: NewPassword) -> Message:
105111
)
106112
def recover_password_html_content(email: str, session: SessionDep) -> Any:
107113
"""
108-
HTML Content for Password Recovery
114+
HTML content for password recovery.
115+
Generates HTML content of the password recovery email for preview, accessible only to superusers.
109116
"""
110117
user = crud.get_user_by_email(session=session, email=email)
111118

@@ -121,4 +128,4 @@ def recover_password_html_content(email: str, session: SessionDep) -> Any:
121128

122129
return HTMLResponse(
123130
content=email_data.html_content, headers={"subject:": email_data.subject}
124-
)
131+
)

backend/app/crud.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# This script is a set of utility functions that manage user and item creation, updates, and authentication within a FastAPI application using SQLModel.
2+
# Each function interacts with the database through a Session object, performing standard CRUD operations and handling security concerns like password hashing and verification.
3+
# Database interaction functions (CRUD operations) for data. CRUD stands for Create, Read, Update, and Delete
14
import uuid
25
from typing import Any
36

@@ -6,6 +9,7 @@
69
from app.core.security import get_password_hash, verify_password
710
from app.models import Item, ItemCreate, User, UserCreate, UserUpdate
811

12+
# create_user: Creates a new user in the database, hashing the user's password before saving it.
913

1014
def create_user(*, session: Session, user_create: UserCreate) -> User:
1115
db_obj = User.model_validate(
@@ -16,6 +20,7 @@ def create_user(*, session: Session, user_create: UserCreate) -> User:
1620
session.refresh(db_obj)
1721
return db_obj
1822

23+
# update_user: Updates an existing user's information in the database, including re-hashing the password if it’s updated.
1924

2025
def update_user(*, session: Session, db_user: User, user_in: UserUpdate) -> Any:
2126
user_data = user_in.model_dump(exclude_unset=True)
@@ -30,12 +35,14 @@ def update_user(*, session: Session, db_user: User, user_in: UserUpdate) -> Any:
3035
session.refresh(db_user)
3136
return db_user
3237

38+
# get_user_by_email: Retrieves a user from the database based on their email address.
3339

3440
def get_user_by_email(*, session: Session, email: str) -> User | None:
3541
statement = select(User).where(User.email == email)
3642
session_user = session.exec(statement).first()
3743
return session_user
3844

45+
# authenticate: Authenticates a user by verifying their email and password, returning the user if the credentials match.
3946

4047
def authenticate(*, session: Session, email: str, password: str) -> User | None:
4148
db_user = get_user_by_email(session=session, email=email)
@@ -45,10 +52,33 @@ def authenticate(*, session: Session, email: str, password: str) -> User | None:
4552
return None
4653
return db_user
4754

55+
# create_item: Creates a new item in the database, associating it with the owner (a specific user).
4856

4957
def create_item(*, session: Session, item_in: ItemCreate, owner_id: uuid.UUID) -> Item:
5058
db_item = Item.model_validate(item_in, update={"owner_id": owner_id})
5159
session.add(db_item)
5260
session.commit()
5361
session.refresh(db_item)
5462
return db_item
63+
64+
from app.models import GeoFile # Import the GeoFile model
65+
66+
# create_geofile: Adds a new GeoFile record to the database.
67+
68+
def create_geofile(session: Session, file_name: str, file_path: str, description: str = None) -> GeoFile:
69+
geofile = GeoFile(file_name=file_name, file_path=file_path, description=description)
70+
session.add(geofile) # Add the new GeoFile instance to the session.
71+
session.commit() # Commit the transaction to save the GeoFile in the database.
72+
session.refresh(geofile) # Refresh the instance to load any updated database state.
73+
return geofile # Return the created GeoFile object.
74+
75+
# get_geofile: Retrieves a GeoFile record by its unique ID.
76+
77+
def get_geofile(session: Session, geofile_id: uuid.UUID) -> GeoFile | None:
78+
return session.get(GeoFile, geofile_id) # Get the GeoFile by its ID, or None if not found.
79+
80+
# list_geofiles: Retrieves all GeoFile records from the database.
81+
82+
def list_geofiles(session: Session) -> list[GeoFile]:
83+
statement = select(GeoFile) # Prepare a query to select all GeoFile records.
84+
return session.exec(statement).all() # Execute the query and return all results as a list.

0 commit comments

Comments
 (0)