Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Issue #3057] Create Agencies API #3065

Merged
merged 35 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c6a8215
Merge
mikehmassgov Nov 15, 2024
259055b
Merge branch 'main' of https://github.com/HHS/simpler-grants-gov
mikehmassgov Nov 15, 2024
9ccc235
Merge branch 'main' of https://github.com/HHS/simpler-grants-gov
mikehmassgov Nov 17, 2024
d199fb1
Merge branch 'main' of https://github.com/HHS/simpler-grants-gov
mikehmassgov Nov 18, 2024
e8f88b3
Merge branch 'main' of https://github.com/HHS/simpler-grants-gov
mikehmassgov Nov 20, 2024
0af1c0e
Merge branch 'main' of https://github.com/HHS/simpler-grants-gov
mikehmassgov Nov 21, 2024
d682c35
Merge branch 'main' of https://github.com/HHS/simpler-grants-gov
mikehmassgov Nov 22, 2024
23e8354
Merge branch 'main' of https://github.com/HHS/simpler-grants-gov
mikehmassgov Nov 25, 2024
e5ef23e
Merge branch 'main' of https://github.com/HHS/simpler-grants-gov
mikehmassgov Nov 26, 2024
fbc72df
Add agencies API
mikehmassgov Nov 27, 2024
ca36923
Clean up
mikehmassgov Nov 27, 2024
ff4e004
Clean up / format
mikehmassgov Nov 27, 2024
a92e77a
Lint
mikehmassgov Nov 27, 2024
64e756e
Update Dockerfile
mikehmassgov Nov 27, 2024
5463242
Update Dockerfile
mikehmassgov Nov 27, 2024
9b61c2d
Update Dockerfile
mikehmassgov Nov 27, 2024
cbec699
Create ERD diagram and Update OpenAPI spec
nava-platform-bot Nov 27, 2024
cd2674e
Merge branch 'main' into mikehgrantsgov/3057-add-agencies-listing-api
mikehgrantsgov Nov 27, 2024
14a2560
Fix subsequent test?
mikehmassgov Nov 27, 2024
48f2dc4
Merge branch 'mikehgrantsgov/3057-add-agencies-listing-api' of https:…
mikehmassgov Nov 27, 2024
7596bdc
Clean up
mikehmassgov Nov 27, 2024
19ebcbb
Remove cleanup
mikehmassgov Nov 27, 2024
81498e1
Merge branch 'main' into mikehgrantsgov/3057-add-agencies-listing-api
mikehgrantsgov Dec 3, 2024
f4961a6
Clean up agencies via ORM
mikehmassgov Dec 3, 2024
07d3c0a
Merge branch 'mikehgrantsgov/3057-add-agencies-listing-api' of https:…
mikehmassgov Dec 3, 2024
6f217a0
Use class approach
mikehmassgov Dec 3, 2024
021e9a6
Format
mikehmassgov Dec 3, 2024
bf36855
Merge branch 'main' into mikehgrantsgov/3057-add-agencies-listing-api
mikehgrantsgov Dec 3, 2024
62996c7
Fix agency code genderation
mikehmassgov Dec 3, 2024
0efa8be
Add description / filter out test agencies from respose
mikehmassgov Dec 3, 2024
9081f53
Remove is_test_agency from schema
mikehmassgov Dec 3, 2024
9f4a40a
Create ERD diagram and Update OpenAPI spec
nava-platform-bot Dec 3, 2024
f70495e
Update schema and add test for top level agencies
mikehmassgov Dec 5, 2024
b79eb35
Merge branch 'mikehgrantsgov/3057-add-agencies-listing-api' of https:…
mikehmassgov Dec 5, 2024
1a8cdc8
Create ERD diagram and Update OpenAPI spec
nava-platform-bot Dec 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 196 additions & 2 deletions api/openapi.generated.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ tags:
- name: Health
- name: Opportunity v1
- name: Extract v1
- name: Agency v1
- name: User v1
servers: .
paths:
Expand Down Expand Up @@ -86,6 +87,47 @@ paths:
sort_direction: descending
security:
- ApiKeyAuth: []
/v1/agencies:
post:
parameters: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/AgencyListResponse'
description: Successful response
'422':
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Validation error
'401':
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Authentication error
tags:
- Agency v1
summary: Agencies Get
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AgencyListRequest'
examples:
example1:
summary: No filters
value:
pagination:
order_by: created_at
page_offset: 1
page_size: 25
sort_direction: descending
security:
- ApiKeyAuth: []
/v1/users/token:
post:
parameters:
Expand Down Expand Up @@ -670,6 +712,158 @@ components:
type: integer
description: The HTTP status code
example: 200
AgencyFilterV1:
type: object
properties:
agency_id:
type: integer
AgencyPaginationV1:
type: object
properties:
order_by:
type: string
enum:
- created_at
description: The field to sort the response by
sort_direction:
description: Whether to sort the response ascending or descending
enum:
- ascending
- descending
type:
- string
page_size:
type: integer
minimum: 1
description: The size of the page to fetch
example: 25
page_offset:
type: integer
minimum: 1
description: The page number to fetch, starts counting from 1
example: 1
required:
- order_by
- page_offset
- page_size
- sort_direction
AgencyListRequest:
type: object
properties:
filters:
type:
- object
allOf:
- $ref: '#/components/schemas/AgencyFilterV1'
pagination:
type:
- object
allOf:
- $ref: '#/components/schemas/AgencyPaginationV1'
required:
- pagination
AgencyContactInfo:
type: object
properties:
contact_name:
type: string
description: Full name of the agency contact person
address_line_1:
type: string
description: Primary street address of the agency
address_line_2:
type:
- string
- 'null'
description: Additional address information (suite, unit, etc.)
city:
type: string
description: City where the agency is located
state:
type: string
description: State where the agency is located
zip_code:
type: string
description: Postal code for the agency address
phone_number:
type: string
description: Contact phone number for the agency
primary_email:
type: string
description: Main email address for agency communications
secondary_email:
type:
- string
- 'null'
description: Alternative email address for agency communications
AgencyResponse:
type: object
properties:
agency_id:
type: integer
agency_name:
type: string
agency_code:
type: string
sub_agency_code:
type:
- string
- 'null'
assistance_listing_number:
type: string
agency_submission_notification_setting:
type: string
top_level_agency:
type:
- object
allOf:
- $ref: '#/components/schemas/AgencyResponse'
agency_contact_info:
type:
- object
- 'null'
anyOf:
- $ref: '#/components/schemas/AgencyContactInfo'
- type: 'null'
agency_download_file_types:
type: array
description: List of download file types supported by the agency
items:
enum:
- xml
- pdf
type:
- string
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
AgencyListResponse:
type: object
properties:
pagination_info:
description: The pagination information for paginated endpoints
type: *id001
allOf:
- $ref: '#/components/schemas/PaginationInfo'
message:
type: string
description: The message to return
example: Success
data:
type: array
description: A list of agency records
items:
type:
- object
allOf:
- $ref: '#/components/schemas/AgencyResponse'
status_code:
type: integer
description: The HTTP status code
example: 200
User:
type: object
properties:
Expand Down Expand Up @@ -836,7 +1030,7 @@ components:
- archived
type:
- string
AgencyFilterV1:
AgencyFilterV11:
type: object
properties:
one_of:
Expand Down Expand Up @@ -979,7 +1173,7 @@ components:
type:
- object
allOf:
- $ref: '#/components/schemas/AgencyFilterV1'
- $ref: '#/components/schemas/AgencyFilterV11'
assistance_listing_number:
type:
- object
Expand Down
6 changes: 6 additions & 0 deletions api/src/api/agencies_v1/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from src.api.agencies_v1.agency_blueprint import agency_blueprint

# import agency_routes module to register the API routes on the blueprint
import src.api.agencies_v1.agency_routes # noqa: F401 E402 isort:skip

__all__ = ["agency_blueprint"]
9 changes: 9 additions & 0 deletions api/src/api/agencies_v1/agency_blueprint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from apiflask import APIBlueprint

agency_blueprint = APIBlueprint(
"agency_v1",
__name__,
tag="Agency v1",
cli_group="agency_v1",
url_prefix="/v1",
)
54 changes: 54 additions & 0 deletions api/src/api/agencies_v1/agency_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import logging

import src.adapters.db as db
import src.adapters.db.flask_db as flask_db
import src.api.agencies_v1.agency_schema as agency_schema
import src.api.response as response
from src.api.agencies_v1.agency_blueprint import agency_blueprint
from src.auth.api_key_auth import api_key_auth
from src.logging.flask_logger import add_extra_data_to_current_request_logs
from src.services.agencies_v1.get_agencies import AgencyListParams, get_agencies

logger = logging.getLogger(__name__)

examples = {
"example1": {
"summary": "No filters",
"value": {
"pagination": {
"order_by": "created_at",
"page_offset": 1,
"page_size": 25,
"sort_direction": "descending",
},
},
},
}


@agency_blueprint.post("/agencies")
@agency_blueprint.input(
agency_schema.AgencyListRequestSchema,
arg_name="raw_list_params",
examples=examples,
)
@agency_blueprint.output(agency_schema.AgencyListResponseSchema)
@agency_blueprint.auth_required(api_key_auth)
@flask_db.with_db_session()
def agencies_get(db_session: db.Session, raw_list_params: dict) -> response.ApiResponse:
list_params: AgencyListParams = AgencyListParams.model_validate(raw_list_params)

# Call service with params to get results
with db_session.begin():
results, pagination_info = get_agencies(db_session, list_params)

add_extra_data_to_current_request_logs(
{
"response.pagination.total_pages": pagination_info.total_pages,
"response.pagination.total_records": pagination_info.total_records,
}
)
logger.info("Successfully fetched agencies")

# Serialize results
return response.ApiResponse(message="Success", data=results, pagination_info=pagination_info)
74 changes: 74 additions & 0 deletions api/src/api/agencies_v1/agency_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from src.api.schemas.extension import Schema, fields
from src.api.schemas.response_schema import AbstractResponseSchema, PaginationMixinSchema
from src.constants.lookup_constants import AgencyDownloadFileType
from src.pagination.pagination_schema import generate_pagination_schema


class AgencyFilterV1Schema(Schema):
agency_id = fields.Integer()


class AgencyListRequestSchema(Schema):
filters = fields.Nested(AgencyFilterV1Schema())
pagination = fields.Nested(
generate_pagination_schema(
"AgencyPaginationV1Schema",
["created_at"],
),
required=True,
)


class AgencyContactInfoSchema(Schema):
"""Schema for agency contact information"""

contact_name = fields.String(metadata={"description": "Full name of the agency contact person"})
address_line_1 = fields.String(metadata={"description": "Primary street address of the agency"})
address_line_2 = fields.String(
allow_none=True,
metadata={"description": "Additional address information (suite, unit, etc.)"},
)
city = fields.String(metadata={"description": "City where the agency is located"})
state = fields.String(metadata={"description": "State where the agency is located"})
zip_code = fields.String(metadata={"description": "Postal code for the agency address"})
phone_number = fields.String(metadata={"description": "Contact phone number for the agency"})
primary_email = fields.String(
metadata={"description": "Main email address for agency communications"}
)
secondary_email = fields.String(
allow_none=True,
metadata={"description": "Alternative email address for agency communications"},
)


class AgencyResponseSchema(Schema):
"""Schema for agency response"""

agency_id = fields.Integer()
agency_name = fields.String()
agency_code = fields.String()
Comment on lines +48 to +49
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these two are the only real pieces the existing FE use case needs. So not sure if we want to hyper fit that in some sort of AgencyList endpoint. Otherwise I think it's fine to return wider objects.

sub_agency_code = fields.String(allow_none=True)
assistance_listing_number = fields.String()
agency_submission_notification_setting = fields.String() # Enum value

top_level_agency = fields.Nested(lambda: AgencyResponseSchema(exclude=("top_level_agency",)))

# Agency contact info as nested object
agency_contact_info = fields.Nested(AgencyContactInfoSchema, allow_none=True)

# File types as a list of strings
agency_download_file_types = fields.List(
fields.Enum(AgencyDownloadFileType),
metadata={"description": "List of download file types supported by the agency"},
)

# Add timestamps from TimestampMixin
created_at = fields.DateTime()
updated_at = fields.DateTime()


class AgencyListResponseSchema(AbstractResponseSchema, PaginationMixinSchema):
data = fields.List(
fields.Nested(AgencyResponseSchema),
metadata={"description": "A list of agency records"},
)
2 changes: 2 additions & 0 deletions api/src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import src.api.feature_flags.feature_flag_config as feature_flag_config
import src.logging
import src.logging.flask_logger as flask_logger
from src.api.agencies_v1 import agency_blueprint as agencies_v1_blueprint
from src.api.extracts_v1 import extract_blueprint as extracts_v1_blueprint
from src.api.healthcheck import healthcheck_blueprint
from src.api.opportunities_v0 import opportunity_blueprint as opportunities_v0_blueprint
Expand Down Expand Up @@ -134,6 +135,7 @@ def register_blueprints(app: APIFlask) -> None:
app.register_blueprint(opportunities_v0_1_blueprint)
app.register_blueprint(opportunities_v1_blueprint)
app.register_blueprint(extracts_v1_blueprint)
app.register_blueprint(agencies_v1_blueprint)

auth_endpoint_config = AuthEndpointConfig()
if auth_endpoint_config.auth_endpoint:
Expand Down
Loading