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

RFC-Part 5 #83

Merged
merged 4 commits into from
Aug 7, 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
2 changes: 1 addition & 1 deletion strr-api/src/strr_api/models/rental.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class Document(BaseModel):
eligibility_id = db.Column(db.Integer, db.ForeignKey("eligibilities.id"), nullable=False)
file_name = db.Column(db.String, nullable=False)
file_type = db.Column(db.String, nullable=False) # e.g., 'pdf', 'jpeg', etc.
path = db.Column(db.String, nullable=False) # e.g., 'pdf', 'jpeg', etc.
path = db.Column(db.String, nullable=False)

eligibility = relationship("Eligibility", back_populates="documents")

Expand Down
17 changes: 16 additions & 1 deletion strr-api/src/strr_api/requests/RegistrationRequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,14 @@ class Registration:
"""Registration payload object."""

def __init__(
self, primaryContact, unitAddress, unitDetails, listingDetails, principalResidence, secondaryContact=None
self,
primaryContact,
unitAddress,
unitDetails,
listingDetails,
principalResidence,
secondaryContact=None,
documents=[],
):
self.primaryContact = Contact(**primaryContact)
self.secondaryContact = None
Expand All @@ -34,6 +41,7 @@ def __init__(
self.unitDetails = UnitDetails(**unitDetails)
self.listingDetails = [ListingDetails(**item) for item in listingDetails]
self.principalResidence = PrincipalResidence(**principalResidence)
self.documents = [Document(**document) for document in documents]


class PrincipalResidence:
Expand Down Expand Up @@ -121,3 +129,10 @@ def __init__(self, name, dateOfBirth, details, mailingAddress, socialInsuranceNu
self.businessNumber = businessNumber
self.details = ContactDetails(**details)
self.mailingAddress = MailingAddress(**mailingAddress)


class Document:
def __init__(self, fileName: str, fileType: str, fileKey: str):
self.fileName = fileName
self.fileKey = fileKey
self.fileType = fileType
117 changes: 115 additions & 2 deletions strr-api/src/strr_api/resources/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from flasgger import swag_from
from flask import Blueprint, g, jsonify, request
from flask_cors import cross_origin
from werkzeug.utils import secure_filename

from strr_api.common.auth import jwt
from strr_api.enums.enum import ErrorMessage, Role
Expand All @@ -56,8 +57,17 @@
from strr_api.requests import RegistrationRequest
from strr_api.responses import AutoApprovalRecord, Events, LTSARecord
from strr_api.schemas.utils import validate
from strr_api.services import ApplicationService, ApprovalService, EventsService, LtsaService, UserService, strr_pay
from strr_api.services import (
ApplicationService,
ApprovalService,
DocumentService,
EventsService,
LtsaService,
UserService,
strr_pay,
)
from strr_api.services.application_service import APPLICATION_STATES_STAFF_ACTION, APPLICATION_TERMINAL_STATES
from strr_api.validators.DocumentUploadValidator import validate_document_upload
from strr_api.validators.RegistrationRequestValidator import validate_registration_request

logger = logging.getLogger("api")
Expand Down Expand Up @@ -193,7 +203,7 @@ def update_application_payment_details(application_id):
if not application:
raise AuthException()
invoice_details = strr_pay.get_payment_details_by_invoice_id(
jwt, application.paymentAccount, application.invoice_id
jwt, application.payment_account, application.invoice_id
)
application = ApplicationService.update_application_payment_details_and_status(application, invoice_details)
return jsonify(ApplicationService.serialize(application)), HTTPStatus.OK
Expand Down Expand Up @@ -379,3 +389,106 @@ def update_application_status(application_id):
except Exception as exception:
logger.error(exception)
return error_response(ErrorMessage.PROCESSING_ERROR.value, HTTPStatus.INTERNAL_SERVER_ERROR)


@bp.route("/<application_id>/documents", methods=("POST",))
@swag_from({"security": [{"Bearer": []}]})
@cross_origin(origin="*")
@jwt.requires_auth
def upload_registration_supporting_document(application_id):
"""
Upload a supporting document for a STRR application.
---
tags:
- application
parameters:
- in: path
name: application_id
type: integer
required: true
description: Application ID
- name: file
in: formData
type: file
required: true
description: The file to upload
consumes:
- multipart/form-data
responses:
201:
description:
400:
description:
401:
description:
403:
description:
502:
description:
"""

try:
account_id = request.headers.get("Account-Id", None)
file = validate_document_upload(request.files)

# only allow upload for registrations that belong to the user
application = ApplicationService.get_application(application_id=application_id, account_id=account_id)
if not application:
raise AuthException()

filename = secure_filename(file.filename)

document = DocumentService.upload_document(filename, file.content_type, file.read())
return document, HTTPStatus.CREATED
except AuthException as auth_exception:
return exception_response(auth_exception)
except ValidationException as auth_exception:
return exception_response(auth_exception)
except ExternalServiceException as service_exception:
return exception_response(service_exception)


@bp.route("/<application_id>/documents/<document_key>", methods=("DELETE",))
@swag_from({"security": [{"Bearer": []}]})
@cross_origin(origin="*")
@jwt.requires_auth
def delete_document(application_id, document_key):
"""
Delete document.
---
tags:
- application
parameters:
- in: path
name: application_id
type: integer
required: true
description: Application Id
- in: path
name: file_key
type: string
required: true
description: File key from the upload document response
responses:
204:
description:
401:
description:
403:
description:
502:
description:
"""

try:
# only allow upload for registrations that belong to the user
application = ApplicationService.get_application(application_id=application_id)
if not application:
raise AuthException()

DocumentService.delete_document(document_key)
return "", HTTPStatus.NO_CONTENT
except AuthException as auth_exception:
return exception_response(auth_exception)
except ExternalServiceException as external_exception:
return exception_response(external_exception)
127 changes: 8 additions & 119 deletions strr-api/src/strr_api/resources/registrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,13 @@
from flasgger import swag_from
from flask import Blueprint, current_app, g, jsonify, request, send_file
from flask_cors import cross_origin
from werkzeug.utils import secure_filename

from strr_api.common.auth import jwt
from strr_api.enums.enum import RegistrationSortBy, RegistrationStatus
from strr_api.exceptions import (
AuthException,
ExternalServiceException,
ValidationException,
error_response,
exception_response,
)
from strr_api.enums.enum import RegistrationSortBy, RegistrationStatus, Role
from strr_api.exceptions import AuthException, ExternalServiceException, error_response, exception_response
from strr_api.models import User
from strr_api.responses import Document, Events, Pagination, Registration
from strr_api.services import EventsService, GCPStorageService, RegistrationService
from strr_api.validators.DocumentUploadValidator import validate_document_upload
from strr_api.services import DocumentService, EventsService, GCPStorageService, RegistrationService

logger = logging.getLogger("api")
bp = Blueprint("registrations", __name__)
Expand Down Expand Up @@ -227,69 +219,11 @@ def get_registration(registration_id):
return exception_response(auth_exception)


@bp.route("/<registration_id>/documents", methods=("POST",))
@swag_from({"security": [{"Bearer": []}]})
@cross_origin(origin="*")
@jwt.requires_auth
def upload_registration_supporting_document(registration_id):
"""
Upload a supporting document for a STRR registration.
---
tags:
- registration
parameters:
- in: path
name: registration_id
type: integer
required: true
description: ID of the registration
- name: file
in: formData
type: file
required: true
description: The file to upload
consumes:
- multipart/form-data
responses:
201:
description:
400:
description:
401:
description:
403:
description:
502:
description:
"""

try:
file = validate_document_upload(request.files)

# only allow upload for registrations that belong to the user
registration = RegistrationService.get_registration(g.jwt_oidc_token_info, registration_id)
if not registration:
raise AuthException()

filename = secure_filename(file.filename)

document = RegistrationService.save_registration_document(
registration.eligibility.id, filename, file.content_type, file.read()
)
return jsonify(Document.from_db(document).model_dump(mode="json")), HTTPStatus.CREATED
except AuthException as auth_exception:
return exception_response(auth_exception)
except ValidationException as auth_exception:
return exception_response(auth_exception)
except ExternalServiceException as service_exception:
return exception_response(service_exception)


@bp.route("/<registration_id>/documents", methods=("GET",))
@swag_from({"security": [{"Bearer": []}]})
@cross_origin(origin="*")
@jwt.requires_auth
def get_registration_supporting_documents(registration_id):
def get_registration_documents(registration_id):
"""
Get registration supporting documents for given registration id.
---
Expand All @@ -316,7 +250,7 @@ def get_registration_supporting_documents(registration_id):
if not registration:
raise AuthException()

documents = RegistrationService.get_registration_documents(registration_id)
documents = DocumentService.get_registration_documents(registration_id)
return (
jsonify([Document.from_db(document).model_dump(mode="json") for document in documents]),
HTTPStatus.OK,
Expand Down Expand Up @@ -363,7 +297,7 @@ def get_registration_supporting_document_by_id(registration_id, document_id):
if not registration:
raise AuthException()

document = RegistrationService.get_registration_document(registration_id, document_id)
document = DocumentService.get_registration_document(registration_id, document_id)
if not document:
return error_response(HTTPStatus.NOT_FOUND, "Document not found")

Expand Down Expand Up @@ -412,7 +346,7 @@ def get_registration_file_by_id(registration_id, document_id):
if not registration:
raise AuthException()

document = RegistrationService.get_registration_document(registration_id, document_id)
document = DocumentService.get_registration_document(registration_id, document_id)
if not document:
return error_response(HTTPStatus.NOT_FOUND, "Document not found")

Expand All @@ -426,52 +360,6 @@ def get_registration_file_by_id(registration_id, document_id):
return exception_response(external_exception)


@bp.route("/<registration_id>/documents/<document_id>", methods=("DELETE",))
@swag_from({"security": [{"Bearer": []}]})
@cross_origin(origin="*")
@jwt.requires_auth
def delete_registration_supporting_document_by_id(registration_id, document_id):
"""
Delete registration supporting document for given registration id and document id.
---
tags:
- registration
parameters:
- in: path
name: registration_id
type: integer
required: true
description: ID of the registration
- in: path
name: document_id
type: integer
required: true
description: ID of the document
responses:
204:
description:
401:
description:
403:
description:
502:
description:
"""

try:
# only allow upload for registrations that belong to the user
registration = RegistrationService.get_registration(g.jwt_oidc_token_info, registration_id)
if not registration:
raise AuthException()

RegistrationService.delete_registration_document(registration_id, document_id)
return "", HTTPStatus.NO_CONTENT
except AuthException as auth_exception:
return exception_response(auth_exception)
except ExternalServiceException as external_exception:
return exception_response(external_exception)


@bp.route("/<registration_id>/events", methods=("GET",))
@swag_from({"security": [{"Bearer": []}]})
@cross_origin(origin="*")
Expand Down Expand Up @@ -520,6 +408,7 @@ def get_registration_events(registration_id):
@swag_from({"security": [{"Bearer": []}]})
@cross_origin(origin="*")
@jwt.requires_auth
@jwt.has_one_of_roles([Role.STAFF.value])
def issue_registration_certificate(registration_id):
"""
Manually generate and issue a STRR registration certificate.
Expand Down
16 changes: 8 additions & 8 deletions strr-api/src/strr_api/responses/DocumentResponse.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@
class Document(BaseModel):
"""Document response object."""

registration_id: int
document_id: int
file_name: str
file_type: str
registrationId: int
documentId: int
fileName: str
fileType: str

@classmethod
def from_db(cls, source: models.Document):
"""Return a Document object from a database model."""
return cls(
registration_id=source.eligibility.registration_id,
document_id=source.id,
file_name=source.file_name,
file_type=source.file_type,
registrationId=source.eligibility.registration_id,
documentId=source.id,
fileName=source.file_name,
fileType=source.file_type,
)
Loading