-
Notifications
You must be signed in to change notification settings - Fork 4
Adds ability to transfer data from temporary crashes when they are deleted via modal #1968
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
base: main
Are you sure you want to change the base?
Changes from 15 commits
0b25ad5
c90ef63
f95c19e
2f0a64b
a235d9b
7666e47
6e00869
76d0b1e
4b5ebe8
5322cf1
5c46b5f
cb8afe2
9b9a6cd
0ae17fc
6ff5883
68f1950
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,6 +29,7 @@ | |
| _upsert_person_image, | ||
| _delete_person_image, | ||
| _get_person_image_url, | ||
| _transfer_person_image, | ||
| validate_file_size, | ||
| _get_crash_diagram_image_url, | ||
| _upsert_crash_diagram_image, | ||
|
|
@@ -55,6 +56,7 @@ | |
| EDITOR_ROLE_NAME = "editor" | ||
| MAX_IMAGE_SIZE_MEGABYTES = 5 | ||
| CORS_URL = "*" | ||
| CORS_HEADERS = ["Content-Type", "Authorization", "Access-Control-Allow-Origin", CORS_URL] | ||
|
|
||
| app = Flask(__name__) | ||
|
|
||
|
|
@@ -393,14 +395,7 @@ def healthcheck(): | |
|
|
||
|
|
||
| @app.route("/cr3/download/<record_locator>") | ||
| @cross_origin( | ||
| headers=[ | ||
| "Content-Type", | ||
| "Authorization", | ||
| "Access-Control-Allow-Origin", | ||
| CORS_URL, | ||
| ], | ||
| ) | ||
| @cross_origin(headers=CORS_HEADERS) | ||
| @requires_auth | ||
| def download_crash_report(record_locator): | ||
| """A valid access token is required to access this route""" | ||
|
|
@@ -417,14 +412,7 @@ def download_crash_report(record_locator): | |
|
|
||
|
|
||
| @app.route("/images/person/<int:person_id>", methods=["GET"]) | ||
| @cross_origin( | ||
| headers=[ | ||
| "Content-Type", | ||
| "Authorization", | ||
| "Access-Control-Allow-Origin", | ||
| CORS_URL, | ||
| ], | ||
| ) | ||
| @cross_origin(headers=CORS_HEADERS) | ||
| @requires_auth | ||
| # No role requirement - all authenticated users can GET | ||
| def get_person_image(person_id): | ||
|
|
@@ -433,14 +421,7 @@ def get_person_image(person_id): | |
|
|
||
|
|
||
| @app.route("/images/person/<int:person_id>", methods=["DELETE", "PUT"]) | ||
| @cross_origin( | ||
| headers=[ | ||
| "Content-Type", | ||
| "Authorization", | ||
| "Access-Control-Allow-Origin", | ||
| CORS_URL, | ||
| ], | ||
| ) | ||
| @cross_origin(headers=CORS_HEADERS) | ||
| @requires_auth | ||
| @requires_roles(ADMIN_ROLE_NAME, EDITOR_ROLE_NAME) | ||
| @validate_file_size(MAX_IMAGE_SIZE_MEGABYTES) | ||
|
|
@@ -453,31 +434,27 @@ def modify_person_image(person_id): | |
| return jsonify(message="Bad Request"), 400 | ||
|
|
||
|
|
||
| @app.route("/images/crash_diagram/<record_locator>", methods=["GET"]) | ||
| @cross_origin( | ||
| headers=[ | ||
| "Content-Type", | ||
| "Authorization", | ||
| "Access-Control-Allow-Origin", | ||
| CORS_URL, | ||
| ], | ||
| @app.route( | ||
| "/images/person/<int:source_person_id>/transfer/<int:target_person_id>", | ||
| methods=["POST"], | ||
| ) | ||
| @cross_origin(headers=CORS_HEADERS) | ||
| @requires_auth | ||
| def transfer_person_image(source_person_id, target_person_id): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Love to see a new API route come into being! What do you think about renaming this to
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One thing that's missing for this new route is a test or two. Would you mind adding test coverage? |
||
| """Transfers a person image from one person record to another""" | ||
| return _transfer_person_image(source_person_id, target_person_id, s3) | ||
|
|
||
|
|
||
| @app.route("/images/crash_diagram/<record_locator>", methods=["GET"]) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is the important part of the changes in this file. |
||
| @cross_origin(headers=CORS_HEADERS) | ||
| @requires_auth | ||
| # No role requirement - all authenticated users can GET | ||
| def get_crash_diagram_image(record_locator): | ||
| """Retrieves a crash diagram image URL""" | ||
| return _get_crash_diagram_image_url(record_locator, s3) | ||
|
|
||
|
|
||
| @app.route("/images/crash_diagram/<record_locator>", methods=["DELETE", "PUT"]) | ||
| @cross_origin( | ||
| headers=[ | ||
| "Content-Type", | ||
| "Authorization", | ||
| "Access-Control-Allow-Origin", | ||
| CORS_URL, | ||
| ], | ||
| ) | ||
| @cross_origin(headers=CORS_HEADERS) | ||
| @requires_auth | ||
| @requires_roles(ADMIN_ROLE_NAME, EDITOR_ROLE_NAME) | ||
| @validate_file_size(MAX_IMAGE_SIZE_MEGABYTES) | ||
|
|
@@ -491,28 +468,14 @@ def modify_crash_diagram_image(record_locator): | |
|
|
||
|
|
||
| @app.route("/user/test") | ||
| @cross_origin( | ||
| headers=[ | ||
| "Content-Type", | ||
| "Authorization", | ||
| "Access-Control-Allow-Origin", | ||
| CORS_URL, | ||
| ], | ||
| ) | ||
| @cross_origin(headers=CORS_HEADERS) | ||
| @requires_auth | ||
| def user_test(): | ||
| return jsonify(message=dict(current_user)) | ||
|
|
||
|
|
||
| @app.route("/user/list_users") | ||
| @cross_origin( | ||
| headers=[ | ||
| "Content-Type", | ||
| "Authorization", | ||
| "Access-Control-Allow-Origin", | ||
| CORS_URL, | ||
| ], | ||
| ) | ||
| @cross_origin(headers=CORS_HEADERS) | ||
| @requires_auth | ||
| def user_list_users(): | ||
| page = request.args.get("page") | ||
|
|
@@ -530,14 +493,7 @@ def user_list_users(): | |
|
|
||
|
|
||
| @app.route("/user/get_user/<id>") | ||
| @cross_origin( | ||
| headers=[ | ||
| "Content-Type", | ||
| "Authorization", | ||
| "Access-Control-Allow-Origin", | ||
| CORS_URL, | ||
| ], | ||
| ) | ||
| @cross_origin(headers=CORS_HEADERS) | ||
| @requires_auth | ||
| def user_get_user(id): | ||
| endpoint = f"https://{AUTH0_DOMAIN}/api/v2/users/" + id | ||
|
|
@@ -547,8 +503,7 @@ def user_get_user(id): | |
|
|
||
|
|
||
| @app.route("/user/create_user", methods=["POST"]) | ||
| @cross_origin(headers=["Content-Type", "Authorization"]) | ||
| @cross_origin(headers=["Access-Control-Allow-Origin", CORS_URL]) | ||
| @cross_origin(headers=CORS_HEADERS) | ||
| @requires_auth | ||
| @requires_roles(ADMIN_ROLE_NAME) | ||
| def user_create_user(): | ||
|
|
@@ -583,14 +538,7 @@ def user_create_user(): | |
|
|
||
|
|
||
| @app.route("/user/update_user/<id>", methods=["PUT"]) | ||
| @cross_origin( | ||
| headers=[ | ||
| "Content-Type", | ||
| "Authorization", | ||
| "Access-Control-Allow-Origin", | ||
| CORS_URL, | ||
| ], | ||
| ) | ||
| @cross_origin(headers=CORS_HEADERS) | ||
| @requires_auth | ||
| @requires_roles(ADMIN_ROLE_NAME) | ||
| def user_update_user(id): | ||
|
|
@@ -602,14 +550,7 @@ def user_update_user(id): | |
|
|
||
|
|
||
| @app.route("/user/delete_user/<id>", methods=["DELETE"]) | ||
| @cross_origin( | ||
| headers=[ | ||
| "Content-Type", | ||
| "Authorization", | ||
| "Access-Control-Allow-Origin", | ||
| CORS_URL, | ||
| ], | ||
| ) | ||
| @cross_origin(headers=CORS_HEADERS) | ||
| @requires_auth | ||
| @requires_roles(ADMIN_ROLE_NAME) | ||
| def user_delete_user(id): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |
| from utils.graphql import ( | ||
| make_hasura_request, | ||
| GET_PERSON_IMAGE_METADATA, | ||
| GET_PERSON_IMAGE_METADATA_FULL, | ||
| UPDATE_PERSON_IMAGE_METADATA, | ||
| GET_CRASH_DIAGRAM_METADATA, | ||
| UPDATE_CRASH_DIAGRAM_METADATA, | ||
|
|
@@ -284,6 +285,56 @@ def _handle_image_upload(person_id, file, s3): | |
| return new_image_obj_key | ||
|
|
||
|
|
||
| def _transfer_person_image(source_person_id, target_person_id, s3): | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was a little confused about the use of the underscore prefixes in this file, but I went with the flow and named this method similarly. My understanding is that the leading But we do import this method, along with a bunch of similar handler methods in
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1, I have heard of this convention and recognize it when I see it. Thanks for pointing it out -- maybe a spin off issue for the backlog to polish it up?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm yeah thanks for going with the flow. I agree these are not private methods in conventional sense and would support removing the leading underscore. |
||
| """Transfer an image from one person record to another. | ||
|
|
||
| Copies the S3 object to a new key for the target person and updates the | ||
| target person's image metadata in the database. | ||
| """ | ||
| source_obj_key, source_original_filename = _get_person_image_metadata( | ||
| source_person_id | ||
| ) | ||
|
|
||
| if not source_obj_key: | ||
| abort( | ||
| 404, | ||
| description=f"No image found for source person ID: {source_person_id}", | ||
| ) | ||
|
|
||
| source_image_source = make_hasura_request( | ||
| query=GET_PERSON_IMAGE_METADATA_FULL, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can safely update it's annoying that python doesn't have fancy js destructing so you'll have to update all the places that use |
||
| variables={"person_id": source_person_id}, | ||
| )["people_by_pk"]["image_source"] | ||
|
Comment on lines
+304
to
+307
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a I see we only have a In particular, I'm talking about the API failing here, not the potential follow-on NoneType error. As a distant, secondary thought, if you were to split the API call and the deep accessing of a value out of the returned dictionary -- splitting those two operations into two "lines" of code seems like a marginally better way to represent the two steps being taken.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree it's odd we don't have any handlers for the hasura api calls. I believe, Frank, the But yes you're totally right this will just short-circuit the app and flask will return a generic Feels like tech-debt work scoping outside of this work?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just want to cosign Frank's comments here, I don't know enough to be able to assess how likely it could be for this to fail, but just wondering if we should protect it anyway. |
||
|
|
||
| ext = source_obj_key.rsplit(".", 1)[-1] if "." in source_obj_key else "jpg" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it feels kinda odd to fallback to |
||
| target_obj_key = f"{AWS_S3_PERSON_IMAGE_LOCATION}/{target_person_id}.{ext}" | ||
|
|
||
| try: | ||
| s3.copy_object( | ||
| Bucket=AWS_S3_BUCKET, | ||
| CopySource={"Bucket": AWS_S3_BUCKET, "Key": source_obj_key}, | ||
| Key=target_obj_key, | ||
| ) | ||
| except Exception as e: | ||
| current_app.logger.exception(str(e)) | ||
| abort(500, description="Failed to copy image in S3") | ||
|
|
||
| make_hasura_request( | ||
| query=UPDATE_PERSON_IMAGE_METADATA, | ||
| variables={ | ||
| "person_id": target_person_id, | ||
| "object": { | ||
| "image_s3_object_key": target_obj_key, | ||
| "image_source": source_image_source, | ||
| "image_original_filename": source_original_filename, | ||
| "updated_by": get_user_email(), | ||
| }, | ||
| }, | ||
| ) | ||
|
Comment on lines
+322
to
+333
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. along the lines of what Frank said above, but do we need to check the response at all from this request?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| return jsonify(success=True), 200 | ||
|
|
||
|
|
||
| def _delete_person_image(person_id, s3): | ||
| """Delete an image from S3 and nullify its metadata in the db""" | ||
| image_obj_key, image_original_filename = _get_person_image_metadata(person_id) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This wasn't in scope, but when I was in this file, I just noticed how much this array was being repeated for every route and thought I could DRY it up a bit. Hope that's ok. I think there was one route that differed, so I assumed that's intentional and left it as it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm glad you did this -- you cleaned up a lot of repetition, I think this is good.