Skip to content

Commit 954293d

Browse files
committed
add form pages to admin dashboard
1 parent 84470d0 commit 954293d

32 files changed

+5308
-427
lines changed

backend/app/routes/intake.py

Lines changed: 107 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,27 @@
1616
# ===== Schemas =====
1717

1818

19+
class FormResponse(BaseModel):
20+
"""Response schema for form"""
21+
22+
id: UUID
23+
name: str
24+
version: int
25+
type: str
26+
27+
model_config = ConfigDict(from_attributes=True)
28+
29+
1930
class FormSubmissionCreate(BaseModel):
2031
"""Schema for creating a new form submission"""
2132

2233
form_id: Optional[UUID] = Field(
2334
None, description="Form ID (optional - will be auto-detected from formType if not provided)"
2435
)
36+
user_id: Optional[UUID] = Field(
37+
None,
38+
description="Target user ID (admin-only). Defaults to the currently authenticated user.",
39+
)
2540
answers: dict = Field(..., description="Form answers as JSON")
2641

2742

@@ -39,6 +54,7 @@ class FormSubmissionResponse(BaseModel):
3954
user_id: UUID
4055
submitted_at: datetime
4156
answers: dict
57+
form: Optional[FormResponse] = None
4258

4359
model_config = ConfigDict(from_attributes=True)
4460

@@ -68,36 +84,23 @@ class OptionsResponse(BaseModel):
6884
treatments: List[TreatmentResponse]
6985

7086

71-
# ===== Custom Auth Dependencies =====
87+
# ===== Custom Auth Helpers =====
7288

7389

74-
def is_owner_or_admin(user_id: UUID):
90+
async def ensure_owner_or_admin(request: Request, db: Session, user_id: UUID) -> None:
7591
"""
76-
Custom dependency that checks if the current user is either:
77-
1. The owner of the resource (matching user_id)
78-
2. An admin
92+
Utility helper that mirrors the access control applied in read endpoints.
7993
"""
8094

81-
async def validator(
82-
request: Request,
83-
db: Session = Depends(get_db),
84-
authorized: bool = has_roles([UserRole.ADMIN, UserRole.PARTICIPANT, UserRole.VOLUNTEER]),
85-
) -> bool:
86-
# Get current user info from request state (set by auth middleware)
87-
current_user_auth_id = request.state.user_id
88-
current_user = db.query(User).filter(User.auth_id == current_user_auth_id).first()
95+
current_user_auth_id = request.state.user_id
96+
current_user = db.query(User).filter(User.auth_id == current_user_auth_id).first()
8997

90-
if not current_user:
91-
raise HTTPException(status_code=401, detail="User not found")
92-
93-
# Check if user is admin or the owner of the resource
94-
if current_user.role.name == "admin" or current_user.id == user_id:
95-
return True
98+
if not current_user:
99+
raise HTTPException(status_code=401, detail="User not found")
96100

101+
if current_user.role.name != "admin" and current_user.id != user_id:
97102
raise HTTPException(status_code=403, detail="Access denied")
98103

99-
return Depends(validator)
100-
101104

102105
# ===== Router Setup =====
103106

@@ -132,6 +135,17 @@ async def create_form_submission(
132135
if not current_user:
133136
raise HTTPException(status_code=401, detail="User not found")
134137

138+
# Determine the target user for which the submission will be created
139+
target_user = current_user
140+
if submission.user_id:
141+
# Only admins can create submissions on behalf of other users
142+
if current_user.role.name != "admin" and submission.user_id != current_user.id:
143+
raise HTTPException(status_code=403, detail="You can only create forms for yourself")
144+
145+
target_user = db.query(User).filter(User.id == submission.user_id).first()
146+
if not target_user:
147+
raise HTTPException(status_code=404, detail="Target user not found")
148+
135149
# Determine form_id if not provided and derive effective form_type for auth check
136150
form_id = submission.form_id
137151
effective_form_type = None
@@ -160,9 +174,9 @@ async def create_form_submission(
160174
form_id = form.id
161175
else:
162176
# Verify the form exists and is of type 'intake'
163-
form = db.query(Form).filter(Form.id == form_id, Form.type == "intake").first()
177+
form = db.query(Form).filter(Form.id == form_id).first()
164178
if not form:
165-
raise HTTPException(status_code=404, detail="Intake form not found")
179+
raise HTTPException(status_code=404, detail="Form not found")
166180
# Derive effective type from form name
167181
if "Participant" in form.name:
168182
effective_form_type = "participant"
@@ -179,16 +193,23 @@ async def create_form_submission(
179193
# Create the raw form submission record
180194
db_submission = FormSubmission(
181195
form_id=form_id,
182-
user_id=current_user.id, # Always use the current user's ID
196+
user_id=target_user.id,
183197
answers=submission.answers,
184198
)
185199

186200
db.add(db_submission)
187201
db.flush() # Get the submission ID without committing
188202

189-
# Process the form data into structured tables
190-
processor = IntakeFormProcessor(db)
191-
processor.process_form_submission(user_id=str(current_user.id), form_data=submission.answers)
203+
# Process intake forms into structured tables when appropriate
204+
should_process_intake = (
205+
form
206+
and form.type == "intake"
207+
and isinstance(submission.answers, dict)
208+
and submission.answers.get("personal_info")
209+
)
210+
if should_process_intake:
211+
processor = IntakeFormProcessor(db)
212+
processor.process_form_submission(user_id=str(target_user.id), form_data=submission.answers)
192213

193214
# Commit everything together
194215
db.commit()
@@ -224,8 +245,8 @@ async def get_form_submissions(
224245
if not current_user:
225246
raise HTTPException(status_code=401, detail="User not found")
226247

227-
# Build query
228-
query = db.query(FormSubmission).join(Form).filter(Form.type == "intake")
248+
# Build query - include all form types, not just intake
249+
query = db.query(FormSubmission).join(Form)
229250

230251
# Apply filters based on user role
231252
if current_user.role_id == 3: # Admin
@@ -242,11 +263,30 @@ async def get_form_submissions(
242263
if form_id:
243264
query = query.filter(FormSubmission.form_id == form_id)
244265

245-
# Execute query
246-
submissions = query.all()
266+
# Execute query with eager loading of form relationship
267+
from sqlalchemy.orm import joinedload
268+
submissions = query.options(joinedload(FormSubmission.form)).all()
269+
270+
# Build response with form data included
271+
submission_responses = []
272+
for s in submissions:
273+
submission_dict = {
274+
"id": s.id,
275+
"form_id": s.form_id,
276+
"user_id": s.user_id,
277+
"submitted_at": s.submitted_at,
278+
"answers": s.answers,
279+
"form": {
280+
"id": s.form.id,
281+
"name": s.form.name,
282+
"version": s.form.version,
283+
"type": s.form.type,
284+
} if s.form else None,
285+
}
286+
submission_responses.append(FormSubmissionResponse.model_validate(submission_dict))
247287

248288
return FormSubmissionListResponse(
249-
submissions=[FormSubmissionResponse.model_validate(s) for s in submissions], total=len(submissions)
289+
submissions=submission_responses, total=len(submission_responses)
250290
)
251291

252292
except HTTPException:
@@ -268,16 +308,45 @@ async def get_form_submission(
268308
Users can only access their own submissions unless they are admin.
269309
"""
270310
try:
271-
# Get the submission
272-
submission = db.query(FormSubmission).filter(FormSubmission.id == submission_id).first()
311+
# Get the submission with eager loading of form relationship
312+
from sqlalchemy.orm import joinedload
313+
submission = (
314+
db.query(FormSubmission)
315+
.options(joinedload(FormSubmission.form))
316+
.filter(FormSubmission.id == submission_id)
317+
.first()
318+
)
273319

274320
if not submission:
275321
raise HTTPException(status_code=404, detail="Form submission not found")
276322

277323
# Check access permissions
278-
await is_owner_or_admin(submission.user_id)(request, db)
324+
current_user_auth_id = request.state.user_id
325+
current_user = db.query(User).filter(User.auth_id == current_user_auth_id).first()
279326

280-
return FormSubmissionResponse.model_validate(submission)
327+
if not current_user:
328+
raise HTTPException(status_code=401, detail="User not found")
329+
330+
# Check if user is admin or the owner of the resource
331+
if current_user.role.name != "admin" and current_user.id != submission.user_id:
332+
raise HTTPException(status_code=403, detail="Access denied")
333+
334+
# Build response with form data included (same as list endpoint)
335+
submission_dict = {
336+
"id": submission.id,
337+
"form_id": submission.form_id,
338+
"user_id": submission.user_id,
339+
"submitted_at": submission.submitted_at,
340+
"answers": submission.answers,
341+
"form": {
342+
"id": submission.form.id,
343+
"name": submission.form.name,
344+
"version": submission.form.version,
345+
"type": submission.form.type,
346+
} if submission.form else None,
347+
}
348+
349+
return FormSubmissionResponse.model_validate(submission_dict)
281350

282351
except HTTPException:
283352
raise
@@ -306,7 +375,7 @@ async def update_form_submission(
306375
raise HTTPException(status_code=404, detail="Form submission not found")
307376

308377
# Check access permissions
309-
await is_owner_or_admin(submission.user_id)(request, db)
378+
await ensure_owner_or_admin(request, db, submission.user_id)
310379

311380
# Update the submission
312381
submission.answers = update_data.answers
@@ -343,7 +412,7 @@ async def delete_form_submission(
343412
raise HTTPException(status_code=404, detail="Form submission not found")
344413

345414
# Check access permissions
346-
await is_owner_or_admin(submission.user_id)(request, db)
415+
await ensure_owner_or_admin(request, db, submission.user_id)
347416

348417
# Delete the submission
349418
db.delete(submission)

0 commit comments

Comments
 (0)