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

Mc issue 356 set up swagger #57

Merged
merged 6 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
122 changes: 89 additions & 33 deletions resources/Agencies.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,110 @@

from flask import Response
from flask_restx import fields

from middleware.agencies import get_agencies
from middleware.security import api_required
from resources.resource_helpers import add_api_key_header_arg
from utilities.namespace import create_namespace
from resources.PsycopgResource import PsycopgResource, handle_exceptions


approved_columns = [
"name",
"homepage_url",
"count_data_sources",
"agency_type",
"multi_agency",
"submitted_name",
"jurisdiction_type",
"state_iso",
"municipality",
"zip_code",
"county_fips",
"county_name",
"lat",
"lng",
"data_sources",
"no_web_presence",
"airtable_agency_last_modified",
"data_sources_last_updated",
"approved",
"rejection_reason",
"last_approval_editor",
"agency_created",
"county_airtable_uid",
"defunct_year",
"airtable_uid",
]
namespace_agencies = create_namespace()

inner_model = namespace_agencies.model(
"Agency Item",
{
"name": fields.String(required=True, description="The name of the agency"),
"homepage_url": fields.String(description="The homepage URL of the agency"),
"count_data_sources": fields.Integer(description="The count of data sources"),
"agency_type": fields.String(description="The type of the agency"),
"multi_agency": fields.Boolean(
description="Indicates if the agency is multi-agency"
),
"submitted_name": fields.String(description="The submitted name of the agency"),
"jurisdiction_type": fields.String(
description="The jurisdiction type of the agency"
),
"state_iso": fields.String(description="The ISO code of the state"),
"municipality": fields.String(description="The municipality of the agency"),
"zip_code": fields.String(description="The ZIP code of the agency"),
"county_fips": fields.String(description="The FIPS code of the county"),
"county_name": fields.String(description="The name of the county"),
"lat": fields.Float(description="The latitude of the agency location"),
"lng": fields.Float(description="The longitude of the agency location"),
"data_sources": fields.String(
description="The data sources related to the agency"
),
"no_web_presence": fields.Boolean(
description="Indicates if the agency has no web presence"
),
"airtable_agency_last_modified": fields.DateTime(
description="The last modified date in Airtable"
),
"data_sources_last_updated": fields.DateTime(
description="The last updated date of data sources"
),
"approved": fields.Boolean(description="Indicates if the agency is approved"),
"rejection_reason": fields.String(
description="The reason for rejection, if any"
),
"last_approval_editor": fields.String(description="The last approval editor"),
"agency_created": fields.DateTime(
description="The creation date of the agency"
),
"county_airtable_uid": fields.String(
description="The Airtable UID of the county"
),
"defunct_year": fields.Integer(
description="The year the agency became defunct"
),
"airtable_uid": fields.String(description="The Airtable UID of the agency"),
},
)

output_model = namespace_agencies.model(
"Agencies Result",
{
'count': fields.Integer(description='Total count of agencies'),
"data": fields.List(fields.Nested(inner_model), description='List of agencies'),
},
)

parser = namespace_agencies.parser()
parser.add_argument(
"page",
type=int,
required=True,
location="args",
help="The page number of results to return.",
default=1,
)
add_api_key_header_arg(parser)

@namespace_agencies.route("/agencies/<page>")
@namespace_agencies.expect(parser)
@namespace_agencies.doc(
description="Get a paginated list of approved agencies from the database.",
responses={
200: "Success. Returns a paginated list of approved agencies.",
500: "Internal server error.",
403: "Unauthorized. Forbidden or an invalid API key.",
400: "Bad request. Missing or bad API key",
},
)
class Agencies(PsycopgResource):
"""Represents a resource for fetching approved agency data from the database."""

@handle_exceptions
@api_required
def get(self, page: str) -> Response:
@namespace_agencies.response(
200,
"Success. Returns a paginated list of approved agencies.",
model=output_model,
)
# @namespace_agencies.marshal_with(output_model)
def get(self, page: int) -> Response:
"""
Retrieves a paginated list of approved agencies from the database.

Parameters:
- page (str): The page number of results to return.

Returns:
- dict: A dictionary containing the count of returned agencies and their data.
"""
Expand Down
27 changes: 27 additions & 0 deletions resources/ApiKey.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,51 @@
from flask import request, Response
from flask_restx import fields

from middleware.login_queries import get_api_key_for_user
from resources.resource_helpers import create_user_model
from utilities.namespace import create_namespace

from resources.PsycopgResource import PsycopgResource, handle_exceptions

namespace_api_key = create_namespace()

api_key_model = namespace_api_key.model(
"ApiKey",
{
"api_key": fields.String(
required=True,
description="The generated API key",
example="2bd77a1d7ef24a1dad3365b8a5c6994e"
),
},
)

user = create_user_model(namespace_api_key)

@namespace_api_key.route("/api_key")
@namespace_api_key.expect(user)
@namespace_api_key.doc(
description="Generates an API key for authenticated users.",
responses={
200: "Success",
401: "Invalid email or password",
500: "Internal server error.",
},
)
class ApiKey(PsycopgResource):
"""Represents a resource for generating an API key for authenticated users."""

@handle_exceptions
@namespace_api_key.response(200, "Success", model=api_key_model)
def get(self) -> Response:
"""
Authenticates a user based on provided credentials and generates an API key.

Reads the 'email' and 'password' from the JSON body of the request, validates the user,
and if successful, generates and returns a new API key.

If the email and password match a row in the database, a new API key is created using uuid.uuid4().hex, updated in for the matching user in the users table, and the API key is sent to the user.

Returns:
- dict: A dictionary containing the generated API key, or None if an error occurs.
"""
Expand Down
51 changes: 48 additions & 3 deletions resources/Archives.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from flask_restx import fields

Check warning on line 1 in resources/Archives.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] resources/Archives.py#L1 <100>

Missing docstring in public module
Raw output
./resources/Archives.py:1:1: D100 Missing docstring in public module

from middleware.security import api_required
from middleware.archives_queries import (
archives_get_query,
Expand All @@ -8,10 +10,34 @@
import json
from typing import Dict, Any

from resources.resource_helpers import add_api_key_header_arg
from utilities.namespace import create_namespace
from resources.PsycopgResource import PsycopgResource, handle_exceptions

namespace_archives = create_namespace()
from resources.PsycopgResource import PsycopgResource, handle_exceptions

archives_get_model = namespace_archives.model(
"ArchivesResponse",
{
"id": fields.String(description="The ID of the data source"),
"last_cached": fields.Date(description="The last date the data was cached"),
"source_url": fields.String(description="The URL of the data source"),
"update_frequency": fields.String(description="The archive update frequency of the data source"),
},
)

archives_post_model = namespace_archives.model(
"ArchivesPost",
{
"id": fields.String(description="The ID of the data source", required=True),
"last_cached": fields.Date(description="The last date the data was cached"),
"broken_source_url_as_of": fields.Date(description="The date the source was marked as broken"),
},
)

archives_header_parser = namespace_archives.parser()
add_api_key_header_arg(archives_header_parser)


@namespace_archives.route("/archives")
class Archives(PsycopgResource):
Expand All @@ -21,6 +47,13 @@

@handle_exceptions
@api_required
@namespace_archives.response(200, "Success: Returns a list of archived data sources", archives_get_model)
@namespace_archives.response(400, "Error: Bad request missing or bad API key")
@namespace_archives.response(403, "Error: Unauthorized. Forbidden or an invalid API key")
@namespace_archives.doc(
description="Retrieves archived data sources from the database.",
)
@namespace_archives.expect(archives_header_parser)
def get(self) -> Any:
"""
Retrieves archived data sources from the database.
Expand All @@ -39,14 +72,26 @@

@handle_exceptions
@api_required
@namespace_archives.doc(
description="Updates the archive data based on the provided JSON payload.",
responses={
200: "Success: Returns a status message indicating success or an error message if an exception occurs.",
400: "Error: Bad request missing or bad API key",
403: "Error: Unauthorized. Forbidden or an invalid API key",
500: "Error: Internal server error",
}
)
@namespace_archives.expect(archives_header_parser, archives_post_model)
def put(self) -> Dict[str, str]:
"""
Updates the archive data based on the provided JSON payload.

Expects a JSON payload with archive data source identifiers and updates them in the database.
Expects a JSON payload with archive data source identifiers and updates them in the database. The put method
on the archives endpoint updates the data source matching the passed id, updating the last_cached date if it
alone is passed, or it and the broken_source_url_as_of field and the url_status to 'broken'.

Returns:
- dict: A status message indicating success or an error message if an exception occurs.
- dict: A status message indicating success or an error message if an exception occurs.
"""
json_data = request.get_json()
data = json.loads(json_data)
Expand Down
Loading
Loading