Skip to content

Commit 2fcc10a

Browse files
committed
Finish implement *most* of the user API routes, left some TODOs for later
1 parent 74777b7 commit 2fcc10a

File tree

5 files changed

+131
-18
lines changed

5 files changed

+131
-18
lines changed

api/auth/main.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,6 @@ async def verifier(
141141

142142
return verifier
143143

144-
145144
# @decorator
146145
# def require_admin(func):
147146
# """Require admin status"""

api/projects/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
create_project,
55
return_projects_for_user,
66
router,
7+
UpdateProjectRequest,
78
update_project,
89
)
910

@@ -12,5 +13,6 @@
1213
"create_project",
1314
"return_projects_for_user",
1415
"router",
16+
"UpdateProjectRequest",
1517
"update_project",
1618
]

api/projects/main.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class CreateProjectRequest(BaseModel):
2222

2323
project_name: str
2424

25-
class updateProjectRequest(BaseModel):
25+
class UpdateProjectRequest(BaseModel):
2626
"Update project request from client"
2727

2828
project_id: int
@@ -40,9 +40,10 @@ class Config:
4040

4141
# @protect
4242
@router.post("/api/projects/update")
43+
@require_auth
4344
async def update_project(
4445
request: Request,
45-
project_request: updateProjectRequest,
46+
project_request: UpdateProjectRequest,
4647
session: AsyncSession = Depends(get_db)
4748
):
4849
"""Update project details"""

api/users/main.py

Lines changed: 121 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,153 @@
11
"""Users API routes"""
22

33
# import asyncio
4-
# import datetime
54

65
# import asyncpg
76
# import orjson
8-
# import sqlalchemy
9-
from fastapi import APIRouter # , Depends, HTTPException, Request
10-
# from pydantic import BaseModel
11-
# from sqlalchemy.ext.asyncio import AsyncSession
7+
import sqlalchemy
8+
from fastapi import APIRouter, Request, Depends, Response
9+
from fastapi.exceptions import HTTPException
10+
from pydantic import BaseModel
11+
from typing import Optional
12+
from sqlalchemy.ext.asyncio import AsyncSession
13+
from datetime import datetime, timezone, timedelta
1214
# from sqlalchemy.orm import selectinload
1315

14-
# from api.auth import require_auth
15-
# from db import get_db, engine
16-
# from models.user import User, UserProject
16+
from api.auth.main import require_auth, generate_session_id, Permission # type: ignore
17+
from db import get_db
18+
from models.user import User
1719

1820

1921
router = APIRouter()
2022

23+
class CreateUserRequest(BaseModel):
2124

25+
email: str
26+
27+
class UpdateUserRequest(BaseModel):
28+
29+
id: int
30+
email: Optional[str]
31+
32+
class DeleteUserRequest(BaseModel):
33+
34+
id: int
35+
email: str # for silly, maybe not needed...
36+
37+
# these are all simple ahh db operations...too simple...am i doing something wrong...
2238
# @limiter.limit("3/day")
23-
async def create_user():
39+
@router.post("/api/users/create")
40+
async def create_user(
41+
request: Request,
42+
create_request: CreateUserRequest,
43+
session: AsyncSession = Depends(get_db)
44+
):
2445
"""Create a new user"""
25-
# TODO: implement create user functionality
46+
# TODO: swap out the version in /api/auth/main.py with this one
47+
# TODO: check if this is secure or even better than the current implementation
48+
new_user = User(email=create_request.email)
49+
50+
session.add(new_user)
51+
await session.commit()
52+
await session.refresh(new_user)
2653

54+
return {"success": True}
2755

56+
# there'll be a second endpoint for admins to update
2857
# @protect
29-
async def update_user():
58+
@router.post("api/users/update")
59+
@require_auth
60+
async def update_user(
61+
request: Request,
62+
update_request: UpdateUserRequest,
63+
response: Response,
64+
session: AsyncSession = Depends(get_db)
65+
):
3066
"""Update user details"""
31-
# TODO: implement update user functionality
67+
68+
user_email = request.state.user["sub"]
69+
70+
user_raw = await session.execute(
71+
sqlalchemy.select(User).where(
72+
User.id == update_request.id
73+
)
74+
)
75+
76+
user = user_raw.scalar_one_or_none()
77+
78+
if user is None:
79+
raise HTTPException(401) # user doesn't exist
80+
81+
if user.email != user_email:
82+
raise HTTPException(401) # they're trying to update someone elses email, no!
83+
84+
update_data = update_request.model_dump(exclude_unset=True, exclude={"id"})
85+
86+
for field, value in update_data.items():
87+
setattr(user, field, value)
88+
89+
if update_request.email is not None:
90+
ret_jwt = await generate_session_id(update_request.email)
91+
response.set_cookie(
92+
key="sessionId", value=ret_jwt, httponly=True, secure=True, max_age=604800
93+
)
94+
95+
await session.commit()
96+
await session.refresh(user) #TODO: figure out why we're refreshing every time and if its needed
97+
98+
return {"success": True}
3299

33100

34101
# @protect
35-
async def get_user():
102+
async def get_user(
103+
request: Request,
104+
create_request: CreateUserRequest,
105+
session: AsyncSession = Depends(get_db)
106+
):
36107
"""Get user details"""
37108
# TODO: implement get user functionality
109+
#TODO: Figure out how many users this allows (just yourself? everyone? a subset?)
38110

39111

40112
# @protect
41-
async def delete_user(): # can only delete their own user!!! don't let them delete other users!!!
113+
@router.post("/api/users/delete")
114+
@require_auth
115+
async def delete_user(
116+
request: Request,
117+
delete_request: DeleteUserRequest,
118+
# response: Response,
119+
session: AsyncSession = Depends(get_db)
120+
): # can only delete their own user!!! don't let them delete other users!!!
42121
"""Delete a user account"""
43122
# TODO: implement delete user functionality
44123

124+
user_email = request.state.user["sub"]
125+
126+
user_raw = await session.execute(
127+
sqlalchemy.select(User).where(
128+
User.id == delete_request.id,
129+
User.email == delete_request.email
130+
)
131+
)
132+
133+
user = user_raw.scalar_one_or_none()
134+
135+
if user is None:
136+
raise HTTPException(401) # user doesn't exist
137+
138+
if user.email != user_email:
139+
raise HTTPException(401) # they're trying to delete someone elses email, no!
140+
141+
user.marked_for_deletion = True
142+
user.date_for_deletion = datetime.now(timezone.utc) + timedelta(days=30)
143+
144+
await session.commit()
145+
await session.refresh(user)
146+
147+
return {"success": True}
148+
149+
150+
45151

46152
# disabled for 30 days, no login -> delete
47153

@@ -50,6 +156,7 @@ async def delete_user(): # can only delete their own user!!! don't let them del
50156
async def is_pending_deletion():
51157
"""Check if a user account is pending deletion"""
52158
# TODO: implement is pending deletion functionality
159+
# TODO: figure out how we want to decide if they're able to get deletion status
53160

54161

55162
# async def run():

models/user.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
from sqlalchemy import (
66
ARRAY,
77
JSON,
8-
Column,
98
DateTime,
109
Float,
1110
ForeignKey,
1211
Integer,
1312
SmallInteger,
1413
String,
14+
Boolean
1515
)
1616
from sqlalchemy.orm import declarative_base, relationship, Mapped, MappedColumn
1717

@@ -20,6 +20,8 @@
2020

2121
class User(Base):
2222
"""User table"""
23+
#TODO: i don't know what we need but i sure as hell know we need more than ts
24+
#TODO: dm hana about ts question mark? idk.
2325

2426
__tablename__ = "users"
2527

@@ -29,6 +31,8 @@ class User(Base):
2931
projects: Mapped[list["UserProject"]] = relationship(
3032
"UserProject", back_populates="user", cascade="all, delete-orphan"
3133
)
34+
marked_for_deletion: Mapped[bool] = MappedColumn(Boolean, nullable=False, default=False)
35+
date_for_deletion:
3236

3337

3438
class UserProject(Base):

0 commit comments

Comments
 (0)