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

SFR-1877: rewrite URLs as fulfillment URLs #286

Merged
merged 10 commits into from
Feb 1, 2024
7 changes: 3 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# CHANGELOG
## unreleased version -- v0.12.4
## Added
New /fulfill endpoint with ability to check for NYPL login in Bearer authorization header
Fulfill endpoint returns pre-signed URLs for objects in private buckets when user is logged in
Change default development port to 5050 due to macOS Monterey and higher occupying port 5000 by default

- New /fulfill endpoint with ability to check for NYPL login in Bearer authorization header
- Fulfill endpoint returns pre-signed URLs for objects in private buckets when user is logged in
- Change default development port to 5050 due to macOS Monterey and higher occupying port 5000 by default

## unreleased version -- v0.12.4
## Added
Expand Down
4 changes: 2 additions & 2 deletions api/blueprints/drbEdition.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ def editionFetch(editionID):

filteredFormats = APIUtils.formatFilters(terms)

edition = dbClient.fetchSingleEdition(editionID)
edition = dbClient.fetchSingleEdition(editionID)
if edition:
statusCode = 200
records = dbClient.fetchRecordsByUUID(edition.dcdw_uuids)

responseBody = APIUtils.formatEditionOutput(
edition, records=records, dbClient=dbClient, showAll=showAll, formats=filteredFormats, reader=readerVersion
edition, request=request, records=records, dbClient=dbClient, showAll=showAll, formats=filteredFormats, reader=readerVersion
)
else:
statusCode = 404
Expand Down
4 changes: 2 additions & 2 deletions api/blueprints/drbLink.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from flask import Blueprint, current_app
from flask import Blueprint, current_app, request
from ..db import DBClient
from ..utils import APIUtils
from logger import createLog
Expand All @@ -18,7 +18,7 @@ def linkFetch(linkID):

if link:
statusCode = 200
responseObject = APIUtils.formatLinkOutput(link)
responseObject = APIUtils.formatLinkOutput(link, request=request)
else:
statusCode = 404
responseObject = {'message': 'Unable to locate link #{}'.format(linkID)}
Expand Down
2 changes: 1 addition & 1 deletion api/blueprints/drbSearch.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def standardQuery():
dataBlock = {
'totalWorks': searchResult.hits.total.value,
'works': APIUtils.formatWorkOutput(
works, results, dbClient=dbClient, formats=filteredFormats, reader=readerVersion
works, results, request=request, dbClient=dbClient, formats=filteredFormats, reader=readerVersion
),
'paging': paging,
'facets': facets
Expand Down
8 changes: 5 additions & 3 deletions api/blueprints/drbWork.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def workFetch(uuid):
terms = {}
for param in ['filter']:
terms[param] = APIUtils.extractParamPairs(param, searchParams)

showAll = searchParams.get('showAll', ['true'])[0].lower() != 'false'
readerVersion = searchParams.get('readerVersion', [None])[0]\
or current_app.config['READER_VERSION']
Expand All @@ -30,8 +30,10 @@ def workFetch(uuid):
work = dbClient.fetchSingleWork(uuid)
if work:
statusCode = 200
responseBody = APIUtils.formatWorkOutput(work, None, showAll=showAll, dbClient=dbClient, formats=filteredFormats, reader=readerVersion)

responseBody = APIUtils.formatWorkOutput(work, None, showAll=showAll,
request=request, dbClient=dbClient, formats=filteredFormats, reader=readerVersion
)

else:
statusCode = 404
responseBody = {
Expand Down
36 changes: 31 additions & 5 deletions api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from datetime import datetime
from hashlib import scrypt
from flask import jsonify
from itertools import repeat
from math import ceil
from model import Collection, Edition
import re
Expand Down Expand Up @@ -140,7 +141,7 @@ def formatPagingOptions(page, pageSize, totalHits):

@classmethod
def formatWorkOutput(
cls, works, identifiers, dbClient, showAll=True, formats=None, reader=None
cls, works, identifiers, dbClient, request, showAll=True, formats=None, reader=None,
):
#Multiple formatted works with formats specified
if isinstance(works, list):
Expand Down Expand Up @@ -233,16 +234,25 @@ def addWorkMeta(cls, work, **kwargs):

@classmethod
def formatEditionOutput(
cls, edition, records=None, dbClient=None, showAll=False, formats=None, reader=None
cls, edition, request, records=None, dbClient=None, showAll=False, formats=None, reader=None
):
editionWorkTitle = edition.work.title
editionWorkAuthors = edition.work.authors
editionInCollection = cls.checkEditionInCollection(None, edition, dbClient)

return cls.formatEdition(
formattedEdition = cls.formatEdition(
edition, editionWorkTitle, editionWorkAuthors, editionInCollection, records, formats, showAll=showAll, reader=reader
)

if formattedEdition.get("instances"):
for instance in formattedEdition['instances']:
for item in instance['items']:
# Map over item links and patch with pre-signed URL where necessary
item['links']= list(map(APIUtils.replacePrivateLinkUrl, item['links'], repeat(request)))

return formattedEdition
Comment on lines +247 to +253
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good way to catch item links which the presigned url should replace.



@classmethod
def checkEditionInCollection(cls, work, edition, dbClient):

Expand Down Expand Up @@ -430,7 +440,7 @@ def formatRecord(cls, record, itemsByLink):
return outRecord

@classmethod
def formatLinkOutput(cls, link):
def formatLinkOutput(cls, link, request):
linkItem = dict(link.items[0])
linkItem['item_id'] = link.items[0].id

Expand All @@ -447,6 +457,9 @@ def formatLinkOutput(cls, link):
linkDict['work']['editions'] = [linkEdition]
linkDict['work']['editions'][0]['items'] = [linkItem]

# Amend link to include /fulfill link if appropriate
linkDict = APIUtils.replacePrivateLinkUrl(linkDict, request)

return linkDict

@classmethod
Expand Down Expand Up @@ -545,7 +558,8 @@ def generate_presigned_url(s3_client, client_method, method_parameters, expires_
def getPresignedUrlFromObjectUrl(s3Client, url):
"""
Given the URL of an S3 resource, generate a presigned Amazon S3 URL
that can be used to access that resource.
that can be used to access that resource. This function assumes S3
URLs in the "virtual hosted bucket" style, not deprecated path style.

:param s3_client: A Boto3 Amazon S3 client
:param url: The URL of the desired resource
Expand All @@ -571,3 +585,15 @@ def getPresignedUrlFromObjectUrl(s3Client, url):
{'Bucket': bucketName,'Key': objectKey},
timeValid
)

@staticmethod
def replacePrivateLinkUrl(link, request):
"""
Given a link object, return a link object with the url replaced if
the link has flags indicating it should be fulfilled via /fulfill
"""
if link['flags'].get("edd") or not link['flags'].get("nypl_login"):
return link
else:
link['url'] = request.host + "/fulfill/" + str(link['link_id'])
return link
1 change: 0 additions & 1 deletion tests/helper.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import os


class TestHelpers:
ENV_VARS = {
'POSTGRES_HOST': 'test_psql_host',
Expand Down
13 changes: 7 additions & 6 deletions tests/unit/test_api_edition_blueprint.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from flask import Flask
from flask import Flask, request
import pytest

from api.blueprints.drbEdition import editionFetch
from api.utils import APIUtils


class TestEditionBlueprint:
@pytest.fixture
def mockUtils(self, mocker):
Expand All @@ -15,7 +14,7 @@ def mockUtils(self, mocker):
formatResponseObject=mocker.DEFAULT,
formatEditionOutput=mocker.DEFAULT
)

@pytest.fixture
def testApp(self):
flaskApp = Flask('test')
Expand Down Expand Up @@ -48,7 +47,8 @@ def test_editionFetch_success_noFormat(self, mockUtils, testApp, mocker):

mockUtils['normalizeQueryParams'].assert_called_once()
mockUtils['formatEditionOutput'].assert_called_once_with(
mockEdition, records='testRecords', dbClient=mockDB, showAll=True, formats=[], reader='test'
mockEdition, records='testRecords', dbClient=mockDB, showAll=True, formats=[],
reader='test', request=request
)
mockUtils['formatResponseObject'].assert_called_once_with(
200, 'singleEdition', 'testEdition'
Expand Down Expand Up @@ -86,8 +86,9 @@ def test_editionFetch_success_format(self, mockUtils, testApp, mocker):
mocker.call('filter', queryParams)
])
mockUtils['formatEditionOutput'].assert_called_once_with(
mockEdition, records='testRecords', showAll=True, dbClient=mockDB,
formats=['application/html+edd', 'application/x.html+edd'], reader='test'
mockEdition, records='testRecords', showAll=True,
dbClient=mockDB,request=request,
formats=['application/html+edd', 'application/x.html+edd'], reader='test'
)
mockUtils['formatResponseObject'].assert_called_once_with(
200, 'singleEdition', 'testEdition'
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/test_api_link_blueprint.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from flask import Flask
from flask import Flask, request
import pytest

from api.blueprints.drbLink import linkFetch
Expand Down Expand Up @@ -36,7 +36,7 @@ def test_linkFetch_success(self, mockUtils, testApp, mocker):
assert testAPIResponse == 'singleLinkResponse'
mockDBClient.assert_called_once_with('testDBClient')

mockUtils['formatLinkOutput'].assert_called_once_with('dbLinkRecord')
mockUtils['formatLinkOutput'].assert_called_once_with('dbLinkRecord', request=request)
mockUtils['formatResponseObject'].assert_called_once_with(
200, 'singleLink', 'testLink'
)
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/test_api_search_blueprint.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from flask import Flask
from flask import Flask, request
import pytest

from api.blueprints.drbSearch import standardQuery
from api.utils import APIUtils
from api.elastic import ElasticClientError


class TestSearchBlueprint:
@pytest.fixture
def mockUtils(self, mocker):
Expand Down Expand Up @@ -139,7 +138,8 @@ def test_standardQuery(self, mockUtils, mockHits, testApp, mocker):
testResultIds,
dbClient=mockDB,
formats=['text/html'],
reader='test'
reader='test',
request=request
)

mockUtils['formatResponseObject'].assert_called_once_with(
Expand Down
Loading
Loading