Skip to content

Commit

Permalink
Implement forms API endpoints, complete with API schema, and simplify…
Browse files Browse the repository at this point in the history
… controller logic. Also fix small NoneType bug in AWSMockService.
  • Loading branch information
Joshua-Douglas committed Jun 16, 2024
1 parent b64c04e commit 8d5ce36
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 47 deletions.
2 changes: 1 addition & 1 deletion api/openapi_server/configs/mock_aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def auto_signup_user_after_request(self, response):
# conditional login within our endpoint. The lambda approach
# requires more overhead, and conditional logic within the endpoint
# risks adding a bug to the production code.
if ('signup' in request.endpoint.lower()) and 200 <= response.status_code < 300:
if request.endpoint and ('signup' in request.endpoint.lower()) and 200 <= response.status_code < 300:
email = request.json['email']
if self._auto_signup_user(email):
new_response = response.get_json()
Expand Down
57 changes: 13 additions & 44 deletions api/openapi_server/controllers/forms_controller.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,18 @@
from sqlalchemy import func, select, delete
from openapi_server.repositories.forms import FormsRepository
from openapi_server.models.database import DataAccessLayer

from openapi_server.models.database import DataAccessLayer, Form, Response
from openapi_server.models.schema import form_schema, response_schema, ResponseSchema, Response
def create_form(body):
forms_repo = FormsRepository(DataAccessLayer.session())

def create_form(form_json):
new_form = form_schema.load(form_json)
with DataAccessLayer.session() as session:
session.add(new_form)
return form_schema.dump(new_form), 200
form_id = forms_repo.add_form(body)
form = forms_repo.get_form_json(form_id)
if form:
return form, 200
return {}, 404

def get_form(form_id):
form = None
with DataAccessLayer.session() as session:
form = session.get(Form, form_id)
forms_repo = FormsRepository(DataAccessLayer.session())
form = forms_repo.get_form_json(form_id)
if form:
return form_schema.dump(form), 200
return dict(), 404

def update_responses(response_json, user_id):
for response in response_json:
response["user_id"] = user_id

with DataAccessLayer.session() as session:
new_responses = response_schema.load(response_json)
field_ids = [r.field_id for r in new_responses]
session.execute(
delete(Response)\
.where(Response.user_id == user_id)\
.where(Response.field_id.in_(field_ids))
)
for new_response in new_responses:
session.add(new_response)

def get_responses(form_id, user_id):
with DataAccessLayer.session() as session:
form = session.get(Form, form_id)
field_ids = set()
for group in form.field_groups:
for field in group.fields:
field_ids.add(field.field_id)

responses = session.execute(
select(Response).\
where(Response.user_id == user_id).\
where(Response.field_id.in_(field_ids))
)

return response_schema.dump(responses)
return form, 200
return f"Form with id {form_id} does not exist.", 404
38 changes: 38 additions & 0 deletions api/openapi_server/controllers/responses_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from openapi_server.repositories.forms import FormsRepository
from openapi_server.repositories.user_repo import UserRepository
from openapi_server.models.database import DataAccessLayer

def update_responses(body, form_id, token_info):
with DataAccessLayer.session() as session:
user_repo = UserRepository(session)
forms_repo = FormsRepository(session)
user = user_repo.get_user(token_info['Username'])

form = forms_repo.get_form(form_id)
if not form:
return f"Form with id {form_id} does not exist.", 404

valid_field_ids = form.get_field_ids()
for response in body:
response["user_id"] = user.id
if response["field_id"] not in valid_field_ids:
return f"Form {form_id} does not contain field id {response['field_id']}", 400

forms_repo.add_user_responses(user.id, body)

return {}, 204

def get_responses(form_id, token_info):
with DataAccessLayer.session() as session:
user_repo = UserRepository(session)
forms_repo = FormsRepository(session)

form = forms_repo.get_form_json(form_id)
if not form:
return f"Form with id {form_id} does not exist.", 404

user = user_repo.get_user(token_info['Username'])
responses = forms_repo.get_user_responses(user.id, form_id)
if responses:
return responses, 200
return [], 202
4 changes: 4 additions & 0 deletions api/openapi_server/models/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from sqlalchemy.sql import func
from sqlalchemy.schema import CheckConstraint
from sqlalchemy.types import JSON
from typing import List

Base = declarative_base()

Expand Down Expand Up @@ -56,6 +57,9 @@ class Form(Base):
description = Column(Text)
created_at = Column(DateTime, default=func.current_timestamp())

def get_field_ids(self) -> List[int]:
return [field.field_id for group in self.field_groups for field in group.fields]

class FieldProperties(Base):
__tablename__ = 'field_properties'
properties_id = Column(Integer, primary_key=True)
Expand Down
6 changes: 6 additions & 0 deletions api/openapi_server/openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ paths:
$ref: "./paths/auth/authInvite.yaml"
/auth/confirmInvite:
$ref: "./paths/auth/authConfirmInvite.yaml"
/forms:
$ref: "./paths/post-form.yaml"
/forms/{form_id}:
$ref: "./paths/forms.yaml"
/responses/{form_id}:
$ref: "./paths/responses.yaml"
components:
securitySchemes:
jwt:
Expand Down
23 changes: 23 additions & 0 deletions api/openapi_server/openapi/paths/forms.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
get:
summary: Get a form by ID
operationId: get_form
parameters:
- name: form_id
in: path
required: true
schema:
type: integer
responses:
'200':
description: Form data retrieved successfully
content:
application/json:
schema:
$ref: '../schemas/_index.yaml#/Form'
'404':
description: Form not found
'401':
description: Authentication error
x-openapi-router-controller: openapi_server.controllers.forms_controller
security:
- jwt: ["secret"]
23 changes: 23 additions & 0 deletions api/openapi_server/openapi/paths/post-form.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
post:
summary: Create a new form
operationId: create_form
requestBody:
required: true
content:
application/json:
schema:
$ref: '../schemas/_index.yaml#/Form'
responses:
'200':
description: Successfully created a form
content:
application/json:
schema:
$ref: '../schemas/_index.yaml#/Form'
'401':
description: Authentication error
'404':
description: Not found
x-openapi-router-controller: openapi_server.controllers.forms_controller
security:
- jwt: ["secret"]
46 changes: 46 additions & 0 deletions api/openapi_server/openapi/paths/responses.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
put:
summary: Update user responses for a specific form
operationId: update_responses
parameters:
- name: form_id
in: path
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
$ref: '../schemas/_index.yaml#/FormResponses'
responses:
'401':
description: Authentication error
'204':
description: Responses updated successfully
x-openapi-router-controller: openapi_server.controllers.responses_controller
security:
- jwt: ["secret"]
get:
summary: Get responses for a specific form and user
operationId: get_responses
parameters:
- name: form_id
in: path
required: true
schema:
type: integer
responses:
'200':
description: Successfully retrieved responses
content:
application/json:
schema:
$ref: '../schemas/_index.yaml#/FormResponses'
'401':
description: Authentication error
'404':
description: Responses not found
x-openapi-router-controller: openapi_server.controllers.responses_controller
security:
- jwt: ["secret"]
74 changes: 73 additions & 1 deletion api/openapi_server/openapi/schemas/_index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,76 @@ UserSignupSchema:
password:
type: string
required:
- password
- password
FormResponse:
type: object
properties:
field_id:
type: integer
answer_text:
type: string
FormResponses:
type: array
items:
$ref: '#/FormResponse'
FieldValidations:
type: object
properties:
required:
type: boolean
max_length:
type: integer
nullable: true
FieldProperties:
type: object
properties:
description:
type: string
field_type:
type: string
enum:
- date
- dropdown
- multiple_choice
- email
- file_upload
- group
- long_text
- number
- short_text
- yes_no
choices:
type: array
items:
type: string
Field:
type: object
properties:
ref:
type: string
properties:
$ref: '#/FieldProperties'
validations:
$ref: '#/FieldValidations'
FieldGroup:
type: object
properties:
title:
type: string
description:
type: string
fields:
type: array
items:
$ref: '#/Field'
Form:
type: object
properties:
title:
type: string
description:
type: string
field_groups:
type: array
items:
$ref: '#/FieldGroup'
2 changes: 1 addition & 1 deletion api/tests/test_alembic_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def test_db_session_version(empty_db_session):

# Before updating to the new revision please add additional
# test cases below that check the integrity of your new migration
assert DataAccessLayer.revision_id() == 'e4c8bb426528'
assert DataAccessLayer.revision_id() == 'cfc4e41b69d3'

def test_user_roles_available(empty_db_session):
'''
Expand Down

0 comments on commit 8d5ce36

Please sign in to comment.