def put(self, mine_guid, variance_guid): parser = CustomReqparser() # Arguments required by MineDocument parser.add_argument('document_name', type=str, required=True) parser.add_argument('document_manager_guid', type=str, required=True) parser.add_argument('variance_document_category_code', type=str, required=True) variance = Variance.find_by_variance_guid(variance_guid) if not variance: raise NotFound('Unable to fetch variance.') data = parser.parse_args() document_name = data.get('document_name') document_manager_guid = data.get('document_manager_guid') # Register new file upload mine_doc = MineDocument(mine_guid=mine_guid, document_manager_guid=document_manager_guid, document_name=document_name) if not mine_doc: raise BadRequest('Unable to register uploaded file as document') # Associate Variance & MineDocument to create Variance Document # Add fields specific to Variance Documents mine_doc.save() variance_doc = VarianceDocumentXref( mine_document_guid=mine_doc.mine_document_guid, variance_id=variance.variance_id, variance_document_category_code=data.get('variance_document_category_code')) variance.documents.append(variance_doc) variance.save() return variance
class NOWApplicationImportResource(Resource, UserMixin): parser = CustomReqparser() parser.add_argument('mine_guid', type=str, help='guid of the mine.', required=True) @requires_role_edit_permit @api.expect(parser) def post(self, application_guid): data = self.parser.parse_args() mine_guid = data.get('mine_guid') mine = Mine.find_by_mine_guid(mine_guid) if not mine: raise NotFound('Mine not found') now_application_identity = NOWApplicationIdentity.query.filter_by( now_application_guid=application_guid).first() if not now_application_identity: raise NotFound('No identity record for this application guid.') application = transmogrify_now(now_application_identity) application.mine_guid = mine_guid application.now_application_guid = application_guid application.save() return {'now_application_guid': str(application.now_application_guid)}
class PartyOrgBookEntityListResource(Resource, UserMixin): parser = CustomReqparser() parser.add_argument( 'credential_id', type=int, help= 'The latest credential ID of the OrgBook entity to associate the party with.', required=True) @api.expect(parser) @api.doc(description='Create a Party OrgBook Entity.') @requires_role_edit_party @api.marshal_with(PARTY_ORGBOOK_ENTITY, code=201) def post(self, party_guid): party = Party.find_by_party_guid(party_guid) if party is None: raise NotFound('Party not found.') if PartyOrgBookEntity.find_by_party_guid(party_guid) is not None: raise BadRequest( 'This party is already associated with an OrgBook entity.') data = PartyOrgBookEntityListResource.parser.parse_args() credential_id = data.get('credential_id') if PartyOrgBookEntity.find_by_credential_id(credential_id) is not None: raise BadRequest( 'An OrgBook entity with the provided credential ID already exists.' ) resp = OrgBookService.get_credential(credential_id) if resp.status_code != requests.codes.ok: raise BadGateway( f'OrgBook API responded with {resp.status_code}: {resp.reason}' ) try: credential = json.loads(resp.text) registration_id = credential['topic']['source_id'] registration_status = not (credential['inactive']) registration_date = credential['effective_date'] name_id = credential['names'][0]['id'] name_text = credential['names'][0]['text'] except: raise BadGateway('OrgBook API responded with unexpected data.') party_orgbook_entity = PartyOrgBookEntity.create( registration_id, registration_status, registration_date, name_id, name_text, credential_id, party_guid) if not party_orgbook_entity: raise InternalServerError( 'Failed to create the Party OrgBook Entity.') party_orgbook_entity.save() party.party_name = name_text party.save() return party_orgbook_entity, 201
class NOWApplicationDocumentGenerateResource(Resource, UserMixin): parser = CustomReqparser() parser.add_argument('now_application_guid', type=str, location='json', required=True) parser.add_argument('template_data', type=dict, location='json', required=True) @api.doc( description= 'Generates the specified document for the NoW using the provided template data and issues a one-time token that is used to download the document.', params={ 'document_type_code': 'The code indicating the type of document to generate.' }) @api.marshal_with(NOW_DOCUMENT_DOWNLOAD_TOKEN_MODEL, code=200) @requires_role_edit_permit def post(self, document_type_code): document_type = NOWApplicationDocumentType.query.get( document_type_code) if not document_type: raise NotFound('Document type not found') if not document_type.document_template: raise BadRequest(f'Cannot generate a {document_type.description}') data = self.parser.parse_args() template_data = data['template_data'] ##ENFORCE READ-ONLY CONTEXT DATA enforced_data = [ x for x in document_type.document_template._form_spec_with_context( data['now_application_guid']) if x.get('read-only', False) ] for enforced_item in enforced_data: if template_data.get( enforced_item['id']) != enforced_item['context-value']: current_app.logger.debug( f'OVERWRITING ENFORCED key={enforced_item["id"]}, value={template_data.get(enforced_item["id"])} -> {enforced_item["context-value"]}' ) template_data[enforced_item['id']] = enforced_item['context-value'] token = uuid.uuid4() # For now, we don't have a "proper" means of authorizing communication between our microservices, so this temporary solution # has been put in place to authorize with the document manager (pass the authorization headers into the token and re-use them # later). A ticket (MDS-2744) to set something else up as been created. cache.set( NOW_DOCUMENT_DOWNLOAD_TOKEN(token), { 'document_type_code': document_type_code, 'now_application_guid': data['now_application_guid'], 'template_data': template_data, 'username': User().get_user_username(), 'authorization_header': request.headers['Authorization'] }, TIMEOUT_5_MINUTES) return {'token': token}
def put(self, mine_guid, mine_party_appt_guid): parser = CustomReqparser() # Arguments required by MineDocument parser.add_argument('document_name', type=str, required=True) parser.add_argument('document_manager_guid', type=str, required=True) mine_party_appt = MinePartyAppointment.find_by_mine_party_appt_guid( mine_party_appt_guid) if not mine_party_appt: raise NotFound('Mine Party Appointment.') data = parser.parse_args() document_name = data.get('document_name') document_manager_guid = data.get('document_manager_guid') # Register new file upload mine_doc = MineDocument(mine_guid=mine_guid, document_manager_guid=document_manager_guid, document_name=document_name) if not mine_doc: raise BadRequest('Unable to register uploaded file as document') mine_doc.save() mine_party_appt.documents.append(mine_doc) mine_party_appt.save() return mine_party_appt
def put(self, mine_guid, variance_guid): parser = CustomReqparser() # Arguments required by MineDocument parser.add_argument('document_name', type=str, required=True) parser.add_argument('document_manager_guid', type=str, required=True) variance = Variance.find_by_variance_guid(variance_guid) if not variance: raise NotFound('Unable to fetch variance.') data = parser.parse_args() document_name = data.get('document_name') document_manager_guid = data.get('document_manager_guid') # Register new file upload mine_doc = MineDocument(mine_guid=mine_guid, document_manager_guid=document_manager_guid, document_name=document_name) if not mine_doc: raise BadRequest('Unable to register uploaded file as document') variance.mine_documents.append(mine_doc) variance.save() return variance
class NOWApplicationDocumentGenerateResource(Resource, UserMixin): parser = CustomReqparser() parser.add_argument('now_application_guid', type=str, location='json', required=True) parser.add_argument('template_data', type=dict, location='json', required=True) @api.doc( description= 'Generates the specified document for the NoW using the provided template data and issues a one-time token that is used to download the document.', params={ 'document_type_code': 'The code indicating the type of document to generate.' }) @api.marshal_with(NOW_DOCUMENT_DOWNLOAD_TOKEN_MODEL, code=200) @requires_role_edit_permit def post(self, document_type_code): document_type = NOWApplicationDocumentType.query.get( document_type_code) if not document_type: raise NotFound('Document type not found') if not document_type.document_template: raise BadRequest(f'Cannot generate a {document_type.description}') # TODO: Generate document using the provided data. data = self.parser.parse_args() template_data = data['template_data'] ##ENFORCE READ-ONLY CONTEXT DATA enforced_data = [ x for x in document_type.document_template._form_spec_with_context( data['now_application_guid']) if x.get('read-only', False) ] for enforced_item in enforced_data: if template_data.get( enforced_item['id']) != enforced_item['context-value']: current_app.logger.debug( f'OVERWRITING ENFORCED key={enforced_item["id"]}, value={template_data.get(enforced_item["id"])} -> {enforced_item["context-value"]}' ) template_data[enforced_item['id']] = enforced_item['context-value'] token = uuid.uuid4() cache.set( NOW_DOCUMENT_DOWNLOAD_TOKEN(token), { 'document_type_code': document_type_code, 'now_application_guid': data['now_application_guid'], 'template_data': template_data }, TIMEOUT_5_MINUTES) return {'token': token}
class NOWApplicationImportResource(Resource, UserMixin): parser = CustomReqparser() parser.add_argument('mine_guid', type=str, help='guid of the mine.', required=True) parser.add_argument('longitude', type=lambda x: Decimal(x) if x else None, help='Longitude point for the Notice of Work.', location='json') parser.add_argument('latitude', type=lambda x: Decimal(x) if x else None, help='Latitude point for the Notice of Work.', location='json') @requires_role_edit_permit @api.expect(parser) def post(self, application_guid): data = self.parser.parse_args() mine_guid = data.get('mine_guid') latitude = data.get('latitude') longitude = data.get('longitude') mine = Mine.find_by_mine_guid(mine_guid) if not mine: raise NotFound('Mine not found') now_application_identity = NOWApplicationIdentity.query.filter_by( now_application_guid=application_guid).first() if not now_application_identity: raise NotFound('No identity record for this application guid.') application = transmogrify_now(now_application_identity) application.mine_guid = mine_guid application.latitude = latitude application.longitude = longitude application.now_application_guid = application_guid # This is a first pass but by no means exhaustive solution to preventing the now application from being saved more than once. # In the event of multiple requests being fired simultaneously this can still sometimes fail. db.session.refresh(now_application_identity) if now_application_identity.now_application_id is not None: raise BadRequest('This record has already been imported.') application.save() db.session.refresh(now_application_identity) NROSNOWStatusService.nros_now_status_update( now_application_identity.now_number, now_application_identity.now_application.status.description, now_application_identity.now_application.status_updated_date. strftime("%Y-%m-%dT%H:%M:%S")) return {'now_application_guid': str(application.now_application_guid)}
class MineReportCommentResource(Resource, UserMixin): parser = CustomReqparser() parser.add_argument('report_comment', type=str, location='json') parser.add_argument('comment_visibility_ind', type=inputs.boolean, location='json') @api.expect(MINE_REPORT_COMMENT_MODEL) @api.doc(description='update a comment') @api.marshal_with(MINE_REPORT_COMMENT_MODEL, code=201) @requires_role_edit_report def put(self, mine_guid, mine_report_guid, mine_report_comment_guid=None): data = self.parser.parse_args() comment = MineReportComment.find_by_guid(mine_report_comment_guid) if not comment: raise NotFound( 'Mine report comment with guid "{mine_report_comment_guid}" not found.' ) current_app.logger.info(f'Updating {comment} with {data}') for key, value in data.items(): setattr(comment, key, value) comment.save() return comment, 201 @api.doc( description='Delete a mine report comment by guid', params={'mine_report_comment_guid': 'guid of the comment to delete.'}) @requires_role_mine_admin def delete(self, mine_guid, mine_report_guid, mine_report_comment_guid): if mine_report_comment_guid is None: return self.create_error_payload( 404, 'Must provide a mine report comment guid.'), 404 comment = MineReportComment.find_by_guid(mine_report_comment_guid) if not comment: raise NotFound( 'Mine report comment with guid "{mine_report_comment_guid}" not found.' ) comment.deleted_ind = True current_app.logger.info(f'Deleting {comment}') comment.save() return ('', 204)
def put(self, mine_guid, mine_incident_guid, mine_document_guid): parser = CustomReqparser() parser.add_argument('filename', type=str, required=True) parser.add_argument('document_manager_guid', type=str, required=True) parser.add_argument('mine_incident_document_type', type=str, required=True) mine_incident = MineIncident.find_by_mine_incident_guid( mine_incident_guid) mine = Mine.find_by_mine_guid(mine_guid) if not mine_incident: raise NotFound('Mine incident not found') if not mine: raise NotFound('Mine not found.') data = parser.parse_args() document_manager_guid = data.get('document_manager_guid') file_name = data.get('filename') mine_incident_document_type = data.get('mine_incident_document_type') mine_doc = MineDocument(mine_guid=mine.mine_guid, document_name=file_name, document_manager_guid=document_manager_guid) if not mine_doc: raise BadRequest('Unable to register uploaded file as document') mine_doc.save() mine_incident_doc = MineIncidentDocumentXref( mine_document_guid=mine_doc.mine_document_guid, mine_incident_id=mine_incident.mine_incident_id, mine_incident_document_type_code=mine_incident_document_type if mine_incident_document_type else 'INI') mine_incident.documents.append(mine_incident_doc) mine_incident.save() return mine_incident
class MineCommentListResource(Resource, UserMixin): parser = CustomReqparser() parser.add_argument('mine_comment', type=str, location='json') @api.doc(description='Retrive a list of comments for a mine') @api.marshal_with(MINE_COMMENT_MODEL, envelope='records', code=200) @requires_role_view_all def get(self, mine_guid): mine = Mine.find_by_mine_guid(mine_guid) if not mine: raise NotFound('Mine not found') current_app.logger.info(f'Retrieving comments for {mine}') comments = [comment.__dict__ for comment in mine.comments] return comments, 200 @api.expect(MINE_COMMENT_MODEL) @api.doc(description='creates a new comment for the Mine') @api.marshal_with(MINE_COMMENT_MODEL, code=201) @requires_role_mine_edit def post(self, mine_guid): mine = Mine.find_by_mine_guid(mine_guid) if not mine: raise NotFound('Mine not found') data = self.parser.parse_args() if not data['mine_comment']: raise BadRequest('Empty comment') mine_comment = MineComment.create( mine=mine, mine_comment=data['mine_comment'], ) current_app.logger.info(f'Creating comment {mine_comment}') mine_comment.save() return mine_comment, 201
class MineReportCommentListResource(Resource, UserMixin): parser = CustomReqparser() parser.add_argument('report_comment', type=str, location='json') parser.add_argument('comment_visibility_ind', type=inputs.boolean, location='json') @api.doc( description='retrive a list of comments for all report submissions') @api.marshal_with(MINE_REPORT_COMMENT_MODEL, envelope='records', code=200) @requires_role_view_all def get(self, mine_guid, mine_report_guid): mine_report = MineReport.find_by_mine_report_guid(mine_report_guid) if not mine_report: raise NotFound('Mine report not found') mine_report_submissions = mine_report.mine_report_submissions if not mine_report_submissions: raise NotFound('No mine report submissions for this report') current_app.logger.info(f'Retrieving comments for {mine_report}') comments = [ comment.__dict__ for submission in mine_report_submissions[:-1] for comment in submission.comments ] for comment in comments: comment['from_latest_submission'] = False latest_comments = [ comment.__dict__ for submission in mine_report_submissions[-1:] for comment in submission.comments ] for comment in latest_comments: comment['from_latest_submission'] = True return comments + latest_comments, 200 @api.expect(MINE_REPORT_COMMENT_MODEL) @api.doc(description='creates a new comment for the report submission') @api.marshal_with(MINE_REPORT_COMMENT_MODEL, code=201) @requires_role_edit_report def post(self, mine_guid, mine_report_guid): mine_report_submission = MineReportSubmission.find_latest_by_mine_report_guid( mine_report_guid) if not mine_report_submission: raise NotFound('Mine report submission not found') # TODO: Do we want to create a submission if it doesn't exist? data = self.parser.parse_args() if not data['report_comment']: raise BadRequest('Empty comment') mine_report_comment_guid = uuid.uuid4() mine_report_comment = MineReportComment.create( mine_report_submission, mine_report_comment_guid=mine_report_comment_guid, report_comment=data['report_comment'], comment_visibility_ind=data['comment_visibility_ind'], ) current_app.logger.info(f'Creating comment {mine_report_comment}') mine_report_comment.save() return mine_report_comment, 201
class MinePartyApptResource(Resource, UserMixin, ErrorMixin): parser = CustomReqparser() parser.add_argument('mine_guid', type=str, help='guid of the mine.') parser.add_argument('party_guid', type=str, help='guid of the party.') parser.add_argument('mine_party_appt_type_code', type=str, help='code for the type of appt.', store_missing=False) parser.add_argument('related_guid', type=str, store_missing=False) parser.add_argument('start_date', type=lambda x: datetime.strptime(x, '%Y-%m-%d') if x else None, store_missing=False) parser.add_argument('end_date', type=lambda x: datetime.strptime(x, '%Y-%m-%d') if x else None, store_missing=False) @api.doc( params={'mine_party_appt_guid': 'mine party appointment serial id'}) @requires_role_view_all def get(self, mine_party_appt_guid=None): relationships = request.args.get('relationships') relationships = relationships.split(',') if relationships else [] if mine_party_appt_guid: mpa = MinePartyAppointment.find_by_mine_party_appt_guid( mine_party_appt_guid) if not mpa: raise NotFound('Mine Party Appointment not found') result = mpa.json(relationships=relationships) else: mine_guid = request.args.get('mine_guid') party_guid = request.args.get('party_guid') types = request.args.getlist('types') #list mpas = MinePartyAppointment.find_by( mine_guid=mine_guid, party_guid=party_guid, mine_party_appt_type_codes=types) result = [x.json(relationships=relationships) for x in mpas] return result @api.doc( params={'mine_party_appt_guid': 'mine party appointment serial id'}) @requires_role_mine_edit def post(self, mine_party_appt_guid=None): if mine_party_appt_guid: raise BadRequest('unexpected mine party appointment guid') data = self.parser.parse_args() new_mpa = MinePartyAppointment( mine_guid=data.get('mine_guid'), party_guid=data.get('party_guid'), mine_party_appt_type_code=data.get('mine_party_appt_type_code'), start_date=data.get('start_date'), end_date=data.get('end_date'), processed_by=self.get_user_info()) if new_mpa.mine_party_appt_type_code == "EOR": new_mpa.assign_related_guid(data.get('related_guid')) if not new_mpa.mine_tailings_storage_facility_guid: raise AssertionError( 'mine_tailings_storage_facility_guid must be provided for Engineer of Record' ) #TODO move db foreign key constraint when services get separated pass if new_mpa.mine_party_appt_type_code == "PMT": new_mpa.assign_related_guid(data.get('related_guid')) if not new_mpa.permit_guid: raise AssertionError( 'permit_guid must be provided for Permittee') #TODO move db foreign key constraint when services get separated pass try: new_mpa.save() except alch_exceptions.IntegrityError as e: if "daterange_excl" in str(e): mpa_type_name = MinePartyAppointmentType.find_by_mine_party_appt_type_code( data.get('mine_party_appt_type_code')).description raise BadRequest( f'Date ranges for {mpa_type_name} must not overlap') return new_mpa.json() @api.doc( params={ 'mine_party_appt_guid': 'mine party appointment guid, this endpoint only respects form data keys: start_date and end_date, and related_guid' }) @requires_role_mine_edit def put(self, mine_party_appt_guid=None): if not mine_party_appt_guid: raise BadRequest('missing mine party appointment guid') data = self.parser.parse_args() mpa = MinePartyAppointment.find_by_mine_party_appt_guid( mine_party_appt_guid) if not mpa: raise NotFound('mine party appointment not found') for key, value in data.items(): if key in ['party_guid', 'mine_guid']: continue elif key == "related_guid": mpa.assign_related_guid(data.get('related_guid')) else: setattr(mpa, key, value) try: mpa.save() except alch_exceptions.IntegrityError as e: if "daterange_excl" in str(e): mpa_type_name = mpa.mine_party_appt_type.description raise BadRequest( f'Date ranges for {mpa_type_name} must not overlap.') return mpa.json() @api.doc(params={ 'mine_party_appt_guid': 'mine party appointment guid to be deleted' }) @requires_role_mine_edit def delete(self, mine_party_appt_guid=None): if not mine_party_appt_guid: raise BadRequest('Expected mine party appointment guid.') data = self.parser.parse_args() mpa = MinePartyAppointment.find_by_mine_party_appt_guid( mine_party_appt_guid) if not mpa: raise NotFound('Mine party appointment not found.') mpa.deleted_ind = True mpa.save() return ('', 204)
class MineVarianceResource(Resource, UserMixin, ErrorMixin): parser = CustomReqparser() parser.add_argument( 'compliance_article_id', type=int, store_missing=False, help='ID representing the MA or HSRCM code to which this variance relates.') parser.add_argument( 'received_date', store_missing=False, help='The date on which the variance application was received.') parser.add_argument( 'variance_application_status_code', type=str, store_missing=False, help='A 3-character code indicating the status type of the variance. Default: REV') parser.add_argument( 'inspector_party_guid', type=str, store_missing=False, help='GUID of the party who inspected the mine during the variance application process.') parser.add_argument( 'note', type=str, store_missing=False, help='A note to include on the variance. Limited to 300 characters.') parser.add_argument( 'parties_notified_ind', type=bool, store_missing=False, help='Indicates if the relevant parties have been notified of variance request and decision.') parser.add_argument( 'issue_date', store_missing=False, help='The date on which the variance was issued.') parser.add_argument( 'expiry_date', store_missing=False, help='The date on which the variance expires.') @api.doc( description='Get a single variance.', params={ 'mine_guid': 'GUID of the mine to which the variance is associated', 'variance_guid': 'GUID of the variance to fetch' }) @requires_any_of([VIEW_ALL, MINESPACE_PROPONENT]) @api.marshal_with(VARIANCE_MODEL, code=200) def get(self, mine_guid, variance_guid): variance = Variance.find_by_mine_guid_and_variance_guid( mine_guid, variance_guid) if variance is None: raise NotFound('Unable to fetch variance') return variance @api.doc( description='Update a variance.', params={ 'mine_guid': 'GUID of the mine to which the variance is associated', 'variance_guid': 'GUID of the variance to update' }) @requires_any_of([EDIT_VARIANCE, MINESPACE_PROPONENT]) @api.marshal_with(VARIANCE_MODEL, code=200) def put(self, mine_guid, variance_guid): variance = Variance.find_by_mine_guid_and_variance_guid( mine_guid, variance_guid) if variance is None: raise NotFound('Unable to fetch variance') data = self.parser.parse_args() inspector_party_guid = data.get('inspector_party_guid') if inspector_party_guid: inspector = Party.find_by_party_guid(inspector_party_guid) if not inspector: raise BadRequest('Unable to find new inspector.') if not 'INS' in inspector.business_roles_codes: raise BadRequest('Party is not an inspector.') variance.inspector_party_guid = inspector_party_guid for key, value in data.items(): if key in ['inspector_party_guid']: continue setattr(variance, key, value) # A manual check to prevent a stack trace dump on a foreign key / # constraint error because global error handling doesn't currently work # with these errors Variance.validate_status_with_other_values( status=variance.variance_application_status_code, issue=variance.issue_date, expiry=variance.expiry_date, inspector=variance.inspector_party_guid) variance.save() return variance
class AuthorizationResource(Resource, UserMixin): parser = CustomReqparser() parser.add_argument('application_guid', type=str, help='', store_missing=False) parser.add_argument('otl_guid', type=str, help='', store_missing=False) ONE_TIME_PASSWORD_TIMEOUT_SECONDS = Config.ONE_TIME_PASSWORD_TIMEOUT_SECONDS @api.doc(description='authorization endpoint is reachable') def get(self): return "OK", 200 @api.doc( description= 'generates and sends OTL to an email address associated with the application' ) def post(self): data = AuthorizationResource.parser.parse_args() application_guid = data.get('application_guid') application = Application.find_by_guid(application_guid) otl_guid = uuid.uuid4() if application is None: raise NotFound( 'No application was found matching the provided reference number' ) html_content = f""" <table width="100%" style="font-size:12.0pt; color:#595959 " > <tr> <td> You have requested access to the Dormant Sites Reclamation Program site to view information about an application (see Reference Number above). <br/> <br/> Use the button below to access your application information and submit payment requests. <br/> This button can only be used once and access expires after four hours. If you need to access the application again, request another link on the website. </td> </tr> <tr> <td> <br/> <table style="margin-left: auto; margin-right: auto;"> <tr> <td style="border-radius: 2px;" bgcolor="#003366" > <a href="{ONE_TIME_LINK_FRONTEND_URL(otl_guid)}" target="_blank" style="padding: 8px 12px; border: 1px solid #003366;border-radius: 2px;font-size: 14px; color: #ffffff;text-decoration: none;font-weight:bold;display: inline-block;"> View Application </a> </td> </tr> </table> <br/> </td> </tr> </table>""" with EmailService() as es: es.send_email_to_applicant( application, f"Dormant Site Reclamation Program – Access Request", html_content) current_app.logger.debug(f"This is a OTL: {otl_guid}") cache.set( str(otl_guid), application_guid, timeout=AuthorizationResource.ONE_TIME_PASSWORD_TIMEOUT_SECONDS) return "OK", 200 @api.doc(description='generates and returns OTP') def put(self): otp_guid = None issued_time_utc = None timeout = AuthorizationResource.ONE_TIME_PASSWORD_TIMEOUT_SECONDS data = AuthorizationResource.parser.parse_args() otl_guid = data.get('otl_guid') app_guid = cache.get(otl_guid) current_app.logger.info(f'this is app_guid: {app_guid}') if otl_guid and app_guid: cache.delete(otl_guid) current_app.logger.info(f"OTL_GUID_VALUE: {cache.get(otl_guid)}") otp_guid = uuid.uuid4() issued_time_utc = datetime.now(timezone.utc) cache.set(str(otp_guid), app_guid, timeout=timeout) else: abort(401) return jsonify({ "OTP": otp_guid, "issued_time_utc": issued_time_utc.strftime("%d %b %Y %H:%M:%S %z"), "timeout_seconds": AuthorizationResource.ONE_TIME_PASSWORD_TIMEOUT_SECONDS, "application_guid": app_guid })
class MineVarianceListResource(Resource, UserMixin): parser = CustomReqparser() parser.add_argument('compliance_article_id', type=int, store_missing=False, help='ID representing the MA or HSRCM code to which this variance relates.', required=True) parser.add_argument('received_date', store_missing=False, help='The date on which the variance application was received.', required=True) parser.add_argument( 'variance_application_status_code', type=str, store_missing=False, help='A 3-character code indicating the status type of the variance. Default: REV') parser.add_argument('applicant_guid', type=str, store_missing=False, help='GUID of the party on behalf of which the application was made.') parser.add_argument( 'inspector_party_guid', type=str, store_missing=False, help='GUID of the party who inspected the mine during the variance application process.') parser.add_argument('note', type=str, store_missing=False, help='A note to include on the variance. Limited to 300 characters.') parser.add_argument( 'parties_notified_ind', type=bool, store_missing=False, help='Indicates if the relevant parties have been notified of variance request and decision.' ) parser.add_argument('issue_date', store_missing=False, help='The date on which the variance was issued.') parser.add_argument('expiry_date', store_missing=False, help='The date on which the variance expires.') @api.doc(description='Get a list of all variances for a given mine.', params={'mine_guid': 'guid of the mine for which to fetch variances'}) @requires_any_of([VIEW_ALL, MINESPACE_PROPONENT]) @api.marshal_with(VARIANCE_MODEL, code=200, envelope='records') def get(self, mine_guid): variances = Variance.find_by_mine_guid(mine_guid) if variances is None: raise BadRequest( 'Unable to fetch variances. Confirm you\'ve provided a valid mine_guid') if len(variances) == 0: mine = Mine.find_by_mine_guid(mine_guid) if mine is None: raise NotFound('Mine') return variances @api.doc(description='Create a new variance for a given mine.', params={'mine_guid': 'guid of the mine with which to associate the variances'}) @api.expect(parser) @requires_any_of([EDIT_VARIANCE, MINESPACE_PROPONENT]) @api.marshal_with(VARIANCE_MODEL, code=200) def post(self, mine_guid): data = self.parser.parse_args() compliance_article_id = data['compliance_article_id'] received_date = data['received_date'] inspector_party_guid = data.get('inspector_party_guid') mine = Mine.find_by_mine_guid(mine_guid) if mine is None: raise NotFound('Mine') # A manual check to prevent a stack trace dump on a foreign key / # constraint error because global error handling doesn't currently work # with these errors variance_application_status_code = data.get('variance_application_status_code') or 'REV' issue_date = data.get('issue_date') expiry_date = data.get('expiry_date') Variance.validate_status_with_other_values(status=variance_application_status_code, issue=issue_date, expiry=expiry_date, inspector=inspector_party_guid) variance = Variance.create( compliance_article_id=compliance_article_id, mine_guid=mine_guid, received_date=received_date, # Optional fields variance_application_status_code=variance_application_status_code, applicant_guid=data.get('applicant_guid'), inspector_party_guid=inspector_party_guid, note=data.get('note'), parties_notified_ind=data.get('parties_notified_ind'), issue_date=issue_date, expiry_date=expiry_date) if not variance: raise BadRequest('Error: Failed to create variance') variance.save() return variance
class MineReportListResource(Resource, UserMixin): parser = CustomReqparser() # required parser.add_argument('submission_year', type=str, location='json', required=True) parser.add_argument('mine_report_definition_guid', type=str, location='json', required=True) parser.add_argument('due_date', location='json', type=lambda x: datetime.strptime(x, '%Y-%m-%d') if x else None) parser.add_argument('permit_guid', type=str, location='json') parser.add_argument('mine_report_submission_status', type=str, location='json') parser.add_argument('received_date', location='json', type=lambda x: datetime.strptime(x, '%Y-%m-%d') if x else None) parser.add_argument('mine_report_submissions', type=list, location='json') @api.marshal_with(MINE_REPORT_MODEL, envelope='records', code=200) @api.doc(description='returns the reports for a given mine.') @requires_any_of([VIEW_ALL, MINESPACE_PROPONENT]) def get(self, mine_guid): mrd_category = request.args.get('mine_report_definition_category') if mrd_category: mine_reports = MineReport.find_by_mine_guid_and_category( mine_guid, mrd_category) else: mine_reports = MineReport.find_by_mine_guid(mine_guid) return mine_reports @api.doc(description='creates a new report for the mine') @api.marshal_with(MINE_REPORT_MODEL, code=201) @requires_any_of([EDIT_REPORT, MINESPACE_PROPONENT]) def post(self, mine_guid): mine = Mine.find_by_mine_guid(mine_guid) if not mine: raise NotFound('Mine not found') data = self.parser.parse_args() mine_report_definition = MineReportDefinition.find_by_mine_report_definition_guid( data['mine_report_definition_guid']) permit = Permit.find_by_permit_guid_or_no(data['permit_guid']) if mine_report_definition is None: raise BadRequest('A report must be selected from the list.') if permit and permit.mine_guid != mine.mine_guid: raise BadRequest( 'The permit must be associated with the selected mine.') mine_report = MineReport.create( mine_report_definition_id=mine_report_definition. mine_report_definition_id, mine_guid=mine.mine_guid, due_date=data.get('due_date'), received_date=data['received_date'], submission_year=data['submission_year'], permit_id=permit.permit_id if permit else None) submissions = data.get('mine_report_submissions') if submissions: submission = submissions[-1] if len(submission.get('documents')) > 0: submission_status = data.get( 'mine_report_submission_status') if data.get( 'mine_report_submission_status') else 'NRQ' report_submission = MineReportSubmission( mine_report_submission_status_code=submission_status, submission_date=datetime.utcnow()) for submission_doc in submission.get('documents'): mine_doc = MineDocument( mine_guid=mine.mine_guid, document_name=submission_doc['document_name'], document_manager_guid=submission_doc[ 'document_manager_guid']) if not mine_doc: raise BadRequest( 'Unable to register uploaded file as document') mine_doc.save() report_submission.documents.append(mine_doc) mine_report.mine_report_submissions.append(report_submission) try: mine_report.save() except Exception as e: raise InternalServerError(f'Error when saving: {e}') return mine_report, 201
class PartyResource(Resource, UserMixin, ErrorMixin): parser = CustomReqparser() parser.add_argument( 'first_name', type=str, help='First name of the party, if the party is a person.', store_missing=False) parser.add_argument( 'party_name', type=str, help= 'Last name of the party (Person), or the Organization name (Organization).', store_missing=False) parser.add_argument('phone_no', type=str, help='The phone number of the party. Ex: 123-123-1234', store_missing=False) parser.add_argument('phone_ext', type=str, help='The extension of the phone number. Ex: 1234', store_missing=False) parser.add_argument('email', type=str, help='The email of the party.', store_missing=False) parser.add_argument('email', type=str, help='The email of the party.', store_missing=False) parser.add_argument('party_type_code', type=str, help='The type of the party. Ex: PER', store_missing=False) parser.add_argument('suite_no', type=str, store_missing=False, help='The suite number of the party address. Ex: 123') parser.add_argument( 'address_line_1', type=str, store_missing=False, help='The first address line of the party address. Ex: 1234 Foo Road') parser.add_argument( 'address_line_2', type=str, store_missing=False, help='The second address line of the party address. Ex: 1234 Foo Road') parser.add_argument( 'city', type=str, store_missing=False, help='The city where the party is located. Ex: FooTown') parser.add_argument( 'sub_division_code', type=str, store_missing=False, help='The region code where the party is located. Ex: BC') parser.add_argument( 'post_code', type=str, store_missing=False, help='The postal code of the party address. Ex: A0B1C2') parser.add_argument( 'job_title', type=str, store_missing=False, help='The job title of the party. Ex "Chief of Inspections"') parser.add_argument('postnominal_letters', type=str, store_missing=False, help='Suffixes for a party name. Ex "BSc, PhD"') parser.add_argument( 'idir_username', type=str, store_missing=False, help='The IDIR username of the party. Ex "IDIR\JSMITH"') PARTY_LIST_RESULT_LIMIT = 25 @api.doc(description='Fetch a party by guid', params={ 'party_guid': 'guid of the party to fetch', }) @requires_role_view_all @api.marshal_with(PARTY, code=200) def get(self, party_guid): party = Party.find_by_party_guid(party_guid) if not party: raise NotFound('Party not found') return party @api.expect(parser) @api.doc(description='Update a party by guid', params={'party_guid': 'guid of the party to update.'}) @requires_role_edit_party @api.marshal_with(PARTY, code=200) def put(self, party_guid): data = PartyResource.parser.parse_args() existing_party = Party.find_by_party_guid(party_guid) if not existing_party: raise NotFound('Party not found.') current_app.logger.info(f'Updating {existing_party} with {data}') for key, value in data.items(): if key in ['party_type_code']: continue # non-editable fields from put setattr(existing_party, key, value) # We are now allowing parties to be created without an address if (data.get('suite_no') or data.get('address_line_1') or data.get('address_line_2') or data.get('city') or data.get('sub_division_code') or data.get('post_code') ): # and check that we are changing the address if len(existing_party.address) == 0: address = Address.create() existing_party.address.append(address) for key, value in data.items(): setattr(existing_party.address[0], key, value) existing_party.save() return existing_party @api.doc(description='Delete a party by guid', params={'party_guid': 'guid of the party to delete.'}) @requires_role_mine_admin def delete(self, party_guid): if party_guid is None: return self.create_error_payload(404, 'Must provide a party guid.'), 404 try: party = Party.find_by_party_guid(party_guid) except DBAPIError: return self.create_error_payload(422, 'Invalid Party guid'), 422 if party is None: return self.create_error_payload( 404, 'Party guid with "{party_guid}" not found.'), 404 mine_party_appts = MinePartyAppointment.find_by_party_guid(party_guid) for mine_party_appt in mine_party_appts: mine_party_appt.deleted_ind = True mine_party_appt.save() party.deleted_ind = True party.save() return ('', 204)
class PartyListResource(Resource, UserMixin ): parser = CustomReqparser() parser.add_argument( 'party_name', type=str, help='Last name of the party (Person), or the Organization name (Organization).', required=True) parser.add_argument('party_type_code', type=str, help='Party type. Person (PER) or Organization (ORG).', required=True) parser.add_argument('name_search', type=str, help='Name or partial name of the party.') parser.add_argument('phone_no', type=str, help='The phone number of the party. Ex: 123-123-1234', required=True) parser.add_argument('last_name', type=str, help='Last name of the party, if the party is a person.') parser.add_argument('first_name', type=str, help='First name of the party, if the party is a person.') parser.add_argument('phone_ext', type=str, help='The extension of the phone number. Ex: 1234') parser.add_argument('email', type=str, help='The email of the party.') parser.add_argument('suite_no', type=str, store_missing=False, help='The suite number of the party address. Ex: 123') parser.add_argument('address_line_1', type=str, store_missing=False, help='The first address line of the party address. Ex: 1234 Foo Road') parser.add_argument('address_line_2', type=str, store_missing=False, help='The second address line of the party address. Ex: 1234 Foo Road') parser.add_argument('city', type=str, store_missing=False, help='The city where the party is located. Ex: FooTown') parser.add_argument('sub_division_code', type=str, store_missing=False, help='The region code where the party is located. Ex: BC') parser.add_argument('post_code', type=str, store_missing=False, help='The postal code of the party address. Ex: A0B1C2') PARTY_LIST_RESULT_LIMIT = 25 @api.doc(description='Fetch a paginated list of parties.', params={ 'first_name': 'First name of party or contact', 'party_name': 'Last name or party name of person or organisation', 'phone_no': 'phone number', 'email': 'email of person or organisation', 'type': 'A person (PER) or organisation (ORG)', 'role': 'A comma separated list of roles to be filtered by', 'sort_field': 'enum[party_name] Default: party_name', 'sort_dir': 'enum[asc, desc] Default: asc', 'business_role': 'A business role or roles to filter on' }) @requires_any_of([VIEW_ALL, MINESPACE_PROPONENT]) @api.marshal_with(PAGINATED_PARTY_LIST, code=200) def get(self): paginated_parties, pagination_details = self.apply_filter_and_search(request.args) if not paginated_parties: raise BadRequest('Unable to fetch parties') return { 'records': paginated_parties.all(), 'current_page': pagination_details.page_number, 'total_pages': pagination_details.num_pages, 'items_per_page': pagination_details.page_size, 'total': pagination_details.total_results, } @api.expect(parser) @api.doc(description='Create a party.') @requires_role_edit_party @api.marshal_with(PARTY, code=200) def post(self, party_guid=None): if party_guid: raise BadRequest('Unexpected party id in Url.') data = PartyListResource.parser.parse_args() party = Party.create( data.get('party_name'), data.get('phone_no'), data.get('party_type_code'), # Nullable fields email=data.get('email'), first_name=data.get('first_name'), phone_ext=data.get('phone_ext')) if not party: raise InternalServerError('Error: Failed to create party') # If no address data is provided do not create an address. if (data.get('suite_no') or data.get('address_line_1') or data.get('address_line_2') or data.get('city') or data.get('sub_division_code') or data.get('post_code')): address = Address.create(suite_no=data.get('suite_no'), address_line_1=data.get('address_line_1'), address_line_2=data.get('address_line_2'), city=data.get('city'), sub_division_code=data.get('sub_division_code'), post_code=data.get('post_code')) party.address.append(address) party.save() return party def apply_filter_and_search(self, args): sort_models = { 'party_name': 'Party', } # Handle ListView request items_per_page = args.get('per_page', 25) if items_per_page == 'all': items_per_page = None else: items_per_page = int(items_per_page) page = args.get('page', 1, type=int) # parse the filter terms first_name_filter_term = args.get('first_name', None, type=str) last_name_filter_term = args.get('last_name', None, type=str) party_name_filter_term = args.get('party_name', None, type=str) name_search_filter_term = args.get('name_search', None, type=str) type_filter_term = args.get('type', None, type=str) role_filter_term = args.get('role', None, type=str) email_filter_term = args.get('email', None, type=str) phone_filter_term = args.get('phone_no', None, type=str) sort_field = args.get('sort_field', 'party_name', type=str) sort_dir = args.get('sort_dir', 'asc', type=str) business_roles = args.getlist('business_role', None) sort_model = sort_models.get(sort_field) conditions = [Party.deleted_ind == False] if first_name_filter_term: conditions.append(Party.first_name.ilike('%{}%'.format(first_name_filter_term))) if last_name_filter_term: conditions.append(Party.party_name.ilike('%{}%'.format(last_name_filter_term))) if party_name_filter_term: conditions.append(Party.party_name.ilike('%{}%'.format(party_name_filter_term))) if email_filter_term: conditions.append(Party.email.ilike('%{}%'.format(email_filter_term))) if type_filter_term: conditions.append(Party.party_type_code.like(type_filter_term)) if phone_filter_term: conditions.append(Party.phone_no.ilike('%{}%'.format(phone_filter_term))) if role_filter_term == "NONE": conditions.append(Party.mine_party_appt == None) if name_search_filter_term: name_search_conditions = [] for name_part in name_search_filter_term.strip().split(" "): name_search_conditions.append(Party.first_name.ilike('%{}%'.format(name_part))) name_search_conditions.append(Party.party_name.ilike('%{}%'.format(name_part))) conditions.append(or_(*name_search_conditions)) contact_query = Party.query.filter(and_(*conditions)) if role_filter_term and not role_filter_term == "NONE": role_filter = MinePartyAppointment.mine_party_appt_type_code.like(role_filter_term) role_query = Party.query.join(MinePartyAppointment).filter(role_filter) contact_query = contact_query.intersect(role_query) if contact_query else role_query if business_roles and len(business_roles) > 0: business_role_filter = PartyBusinessRoleAppointment.party_business_role_code.in_( business_roles) business_role_query = Party.query.join(PartyBusinessRoleAppointment).filter( business_role_filter) contact_query = contact_query.intersect( business_role_query) if contact_query else business_role_query # Apply sorting if sort_model and sort_field and sort_dir: sort_criteria = [{'model': sort_model, 'field': sort_field, 'direction': sort_dir}] contact_query = apply_sort(contact_query, sort_criteria) return apply_pagination(contact_query, page, items_per_page)
class MineReportResource(Resource, UserMixin): parser = CustomReqparser() parser.add_argument('due_date', type=str, location='json', store_missing=False) parser.add_argument('received_date', location='json', store_missing=False, type=lambda x: datetime.strptime(x, '%Y-%m-%d') if x else None) parser.add_argument('submission_year', type=str, location='json', store_missing=False) parser.add_argument('mine_report_submission_status', type=str, location='json') parser.add_argument('mine_report_submissions', type=list, location='json', store_missing=False) @api.marshal_with(MINE_REPORT_MODEL, code=200) @requires_any_of([VIEW_ALL, MINESPACE_PROPONENT]) def get(self, mine_guid, mine_report_guid): mine_report = MineReport.find_by_mine_report_guid(mine_report_guid) if not mine_report: raise NotFound("Mine Report not found") return mine_report @api.expect(parser) @api.marshal_with(MINE_REPORT_MODEL, code=200) @requires_any_of([EDIT_REPORT, MINESPACE_PROPONENT]) def put(self, mine_guid, mine_report_guid): mine = Mine.find_by_mine_guid(mine_guid) mine_report = MineReport.find_by_mine_report_guid(mine_report_guid) if not mine_report or str(mine_report.mine_guid) != mine_guid: raise NotFound("Mine Report not found") data = self.parser.parse_args() if 'due_date' in data: mine_report.due_date = data.get('due_date') if 'received_date' in data: mine_report.received_date = data['received_date'] if 'submission_year' in data: mine_report.submission_year = data['submission_year'] if data.get('mine_report_submission_status') is not None: mine_report_submission_status = data.get( 'mine_report_submission_status') else: mine_report_submission_status = 'NRQ' report_submissions = data.get('mine_report_submissions') submission_iterator = iter(report_submissions) new_submission = next( (x for x in submission_iterator if x.get('mine_report_submission_guid') is None), None) if new_submission is not None: new_report_submission = MineReportSubmission( submission_date=datetime.now(), mine_report_submission_status_code=mine_report_submission_status ) # Copy the current list of documents for the report submission last_submission_docs = mine_report.mine_report_submissions[ -1].documents.copy() if len( mine_report.mine_report_submissions) > 0 else [] # Gets the difference between the set of documents in the new submission and the last submission new_docs = [ x for x in new_submission.get('documents') if not any( str(doc.document_manager_guid) == x['document_manager_guid'] for doc in last_submission_docs) ] # Get the documents that were on the last submission but not part of the new submission removed_docs = [ x for x in last_submission_docs if not any(doc['document_manager_guid'] == str( x.document_manager_guid) for doc in new_submission.get('documents')) ] # Remove the deleted documents from the existing set. for doc in removed_docs: last_submission_docs.remove(doc) if len(last_submission_docs) > 0: new_report_submission.documents.extend(last_submission_docs) for doc in new_docs: mine_doc = MineDocument( mine_guid=mine.mine_guid, document_name=doc['document_name'], document_manager_guid=doc['document_manager_guid']) if not mine_doc: raise BadRequest( 'Unable to register uploaded file as document') mine_doc.save() new_report_submission.documents.append(mine_doc) mine_report.mine_report_submissions.append(new_report_submission) # if the status has changed, update the status of the last submission elif ( len(mine_report.mine_report_submissions) > 0 ) and mine_report_submission_status != mine_report.mine_report_submissions[ -1].mine_report_submission_status_code: mine_report.mine_report_submissions[ -1].mine_report_submission_status_code = mine_report_submission_status try: mine_report.save() except Exception as e: raise InternalServerError(f'Error when saving: {e}') return mine_report @requires_role_edit_report @api.response(204, 'Successfully deleted.') def delete(self, mine_guid, mine_report_guid): mine_report = MineReport.find_by_mine_report_guid(mine_report_guid) if not mine_report or str(mine_report.mine_guid) != mine_guid: raise NotFound("Mine Report not found") mine_report.deleted_ind = True mine_report.save() return None, 204