Skip to content
This repository has been archived by the owner on Jul 25, 2024. It is now read-only.

Commit

Permalink
retrieving file from S3 and retunr presignd url to the user
Browse files Browse the repository at this point in the history
  • Loading branch information
israr-ulhaq committed Jul 5, 2024
1 parent e60c018 commit 69bf8a5
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 78 deletions.
78 changes: 68 additions & 10 deletions app/main/download_data.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import io
import json
from collections import namedtuple
from datetime import datetime
from enum import StrEnum
from typing import Any
Expand Down Expand Up @@ -243,23 +244,80 @@ def process_api_response(query_params: dict) -> tuple:


def process_async_download(query_params: dict):
"""process async download request to start the background process.
:param query_params: (dict): Query parameters for the API request.
"""
request_url = (
Config.DATA_STORE_API_HOST
+ "/trigger_async_download"
+ ("?" + urlencode(query_params, doseq=True) if query_params else "")
)
requests.get(request_url)
requests.post(request_url, timeout=20)


def retrieve_download_file(UUID: str):
if not UUID:
raise ValueError("UUID parameter is required")
def get_presigned_url(filename: str):
"""Get the presigned link for the short time to retrieve the file from s3 bucket.
:param filename (str): object name which needs to be retrieved from s3 if exists
Raises:ValueError: If object doest not exists in S3, it will raise an error.
Returns:Returns the response the API.
"""
if not filename:
raise ValueError("filename is required")

response = get_response(Config.DATA_STORE_API_HOST, f"/retrieve-download/{UUID}")
response = get_response(Config.DATA_STORE_API_HOST, f"/get-presigned-url/{filename}")
return response

# if content_type == MIMETYPE.JSON:
# file_content = io.BytesIO(json.dumps(response.json()).encode("UTF-8"))
# elif content_type == MIMETYPE.XLSX:
# file_content = io.BytesIO(response.content)

return response
FileMetadata = namedtuple("FileMetadata", ["last_modified_date", "file_format", "file_size_str"])


def get_find_download_file_metadata(filename: str) -> FileMetadata:
"""To get the object metadata from S3 using the ovject Key
:param filename (str): object name to get the metadata
Raises:
ValueError: If object doest not exists in S3, it will raise an error.
Returns: FileMetadata:
- Returns the last modified date,
-file format, and human-readable file size.
"""
if not filename:
raise ValueError("filename is required")

try:
response = get_response(Config.DATA_STORE_API_HOST, f"/get-find-download-metadata/{filename}")
print(response.status_code)
metadata = response.json()
file_size = metadata["ContentLength"]
file_size_str = get_human_readable_file_size(file_size)
last_modified_date = metadata["LastModified"]
content_type = metadata["ContentType"]
file_format = ""

if content_type == MIMETYPE.XLSX:
file_format = "Microsoft Excel spreadsheet"
elif content_type == MIMETYPE.JSON:
file_format = "JSON"
else:
file_format = "Unknown type"

return FileMetadata(last_modified_date, file_format, file_size_str)

except requests.exceptions.RequestException as req_err:
raise RuntimeError(f"Request error when getting find file metadata: {req_err}") from req_err


def get_human_readable_file_size(file_size_bytes: int) -> str:
"""Return a human-readable file size string.
:param file_size_bytes: file size in bytes,
:return: human-readable file size,
"""

file_size_kb = round(file_size_bytes / 1024, 1)
if file_size_kb < 1024:
return f"{round(file_size_kb, 1)} KB"
elif file_size_kb < 1024 * 1024:
return f"{round(file_size_kb / 1024, 1)} MB"
else:
return f"{round(file_size_kb / (1024 * 1024), 1)} GB"
2 changes: 1 addition & 1 deletion app/main/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ class DownloadForm(FlaskForm):


class RetrieveForm(FlaskForm):
download = SubmitField("Download Your Data")
download = SubmitField("Download Your Data", widget=GovSubmitInput())
57 changes: 26 additions & 31 deletions app/main/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
current_app,
g,
jsonify,
Response,
)

# isort: on
Expand All @@ -19,19 +18,19 @@
from fsd_utils.authentication.decorators import login_requested, login_required
from werkzeug.exceptions import HTTPException

from app.const import MIMETYPE
from app.main import bp
from app.main.download_data import (
FormNames,
financial_quarter_from_mapping,
financial_quarter_to_mapping,
get_find_download_file_metadata,
get_fund_checkboxes,
get_org_checkboxes,
get_outcome_checkboxes,
get_presigned_url,
get_region_checkboxes,
get_returns,
process_async_download,
retrieve_download_file,
)
from app.main.forms import DownloadForm, RetrieveForm

Expand Down Expand Up @@ -131,47 +130,43 @@ def request_received():
return render_template("request-received.html", user_email=g.user.email)


@bp.route("/retrieve-download/<UUID>", methods=["GET", "POST"])
@bp.route("/get-presigned-url/<filename>", methods=["GET", "POST"])
@login_required(return_app=SupportedApp.POST_AWARD_FRONTEND)
def retrieve_download(UUID: str):
response = retrieve_download_file(UUID)
file_size = int(response.headers.get("content-length", -1))
content_type = response.headers.get("content-type")
file_format = ""

print(content_type)
if content_type == "application/octet-stream":
file_format = "Microsoft spreadsheet"
elif content_type == MIMETYPE.JSON:
file_format = "JSON"
else:
file_format = "Unknow type"

form = RetrieveForm()
context = {"UUID": UUID, "file_format": file_format, "file_size": file_size}
def retrieve_download(filename: str):
"""Get file from S3, send back to user with presigned link
and file metadata, if file is not exist
return file not found page
:param: filename (str):filename of the file which needs to be retrieved with metadata
Returns: redirect to presigned url
"""
response = get_presigned_url(filename)
if response.status_code == 404:
return render_template("file-not-found.html")

file_metadata = get_find_download_file_metadata(filename)
form = RetrieveForm()
context = {
"filename": filename,
"file_size": file_metadata.file_size_str,
"file_format": file_metadata.file_format,
"date": file_metadata.last_modified_date,
}
if form.validate_on_submit():
if response.status_code == 200:
presigned_url = response.json()
try:
user_id = (g.account_id,)
current_app.logger.info(
"Request for download by {{user_id=}}",
"Request for download by user_id={user_id}",
extra={
"user_id": g.account_id,
"user_id": user_id,
"email": g.user.email,
},
)
return Response(
response.iter_content(chunk_size=10 * 1024),
headers={
"Content-Disposition": response.headers.get("Content-Disposition"),
"Content-Type": response.headers.get("Content-Type"),
},
)
return redirect(presigned_url)

except ValueError:
return jsonify({"error": "Invalid response from data store"}), 500
else:
return jsonify({"error": f"Error retrieving file: {response.status_code}"}), response.status_code
else:
return render_template("retrieve-download.html", context=context, form=form)

Expand Down
10 changes: 0 additions & 10 deletions app/static/src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,6 @@
}


.govuk-button {
background-color: #1d70b8;
}


.govuk-button:hover {
background-color: #12066d;
}


.govuk-footer__meta {
display: flex;
margin-right: -15px;
Expand Down
2 changes: 1 addition & 1 deletion app/templates/main/request-received.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ <h2 class="govuk-heading-m govuk-!-margin-top-7">
What happens next
</h2>
<p class="govuk-body">
We will email a link to <span class="govuk-!-font-weight-bold">{{ context["user_email"] }}</span>.
We will email a link to <span class="govuk-!-font-weight-bold">{{ user_email }}</span>.
</p>
<p class="govuk-body">
This may take up to 5 minutes to be delivered to your inbox.</p>
Expand Down
29 changes: 4 additions & 25 deletions app/templates/main/retrieve-download.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,13 @@
<div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-xl">Your data is ready to be downloaded</h1>
<div class="govuk-inset-text">
<p>You requested a data download on <span class="govuk-!-font-weight-bold">{date}</span>.
<br>File format: <span class="govuk-!-font-weight-bold">{{ context.file_format }}, {{ context.file_size }} KB</span></p>
<p>You requested a data download on <span class="govuk-!-font-weight-bold">{{ context.date }}</span>.
<br>File format: <span class="govuk-!-font-weight-bold">{{ context.file_format }}, {{ context.file_size }}</span></p>
</div>
<form id="download-form" method="post" action="{{ url_for('.retrieve_download', uuid=context.UUID) }}">
<form id="download-form" method="post" action="{{ url_for('.retrieve_download', filename=context.filename) }}">
{{ form.csrf_token }}
<input type="hidden" name="uuid" id="download-uuid" value="{{ context.UUID }}">
<!-- {{ form.download }} -->
<button type="submit" class="govuk-button govuk-!-margin-top-3" data-module="govuk-button">
Download your data
</button>
{{ form.download }}
</form>
</div>
</div>
{% endblock content %}

{% block bodyEnd %}
<script>
document.addEventListener('DOMContentLoaded',function(){console.log('JavaScript is working!');});

document.addEventListener('DOMContentLoaded', function() {
const uniqueId = document.getElementById('download-uuid').getAttribute('data-uuid');
document.getElementById('download-uuid').value = uniqueId;

setTimeout(function() {
document.getElementById('download-form').submit();
},
3000
); // 3000 milliseconds = 3 seconds
});
</script>
{% endblock bodyEnd %}

0 comments on commit 69bf8a5

Please sign in to comment.