in kinto-remote-settings/src/kinto_remote_settings/changes/views.py [0:0]
def get_changeset(request):
bid = request.matchdict["bucket_id"]
cid = request.matchdict["collection_id"]
storage = request.registry.storage
queryparams = request.validated["querystring"]
limit = bound_limit(request.registry.settings, queryparams.get("_limit"))
filters = []
include_deleted = False
if "_since" in queryparams:
filters = [Filter("last_modified", queryparams["_since"], COMPARISON.GT)]
# Include tombstones when querying with _since
include_deleted = True
if (bid, cid) == (MONITOR_BUCKET, CHANGES_COLLECTION):
# Redirect old since, on monitor/changes only.
_handle_old_since_redirect(request)
if "bucket" in queryparams:
filters.append(Filter("bucket", queryparams["bucket"], COMPARISON.EQ))
if "collection" in queryparams:
filters.append(
Filter("collection", queryparams["collection"], COMPARISON.EQ)
)
model = ChangesModel(request)
metadata = {}
records_timestamp = model.timestamp()
last_modified = (
records_timestamp # The collection 'monitor/changes' is virtual.
)
# Mimic records endpoint and sort by timestamp desc.
sorting = [Sort("last_modified", -1)]
changes = model.get_objects(
filters=filters,
limit=limit,
include_deleted=include_deleted,
sorting=sorting,
)
else:
bucket_uri = instance_uri(request, "bucket", id=bid)
collection_uri = instance_uri(request, "collection", bucket_id=bid, id=cid)
try:
# We'll make sure that data isn't changed while we read metadata, changes,
# etc.
before = storage.resource_timestamp(
resource_name="record", parent_id=collection_uri
)
# Fetch collection metadata.
metadata = storage.get(
resource_name="collection", parent_id=bucket_uri, object_id=cid
)
except storage_exceptions.ObjectNotFoundError:
raise httpexceptions.HTTPNotFound()
except storage_exceptions.BackendError as e:
# The call to `resource_timestamp()` on an empty collection will try
# initialize it. If the instance is read-only, it fails with a backend
# error. Raise 404 in this case otherwise raise the original backend error.
if "when running in readonly" in str(e):
raise httpexceptions.HTTPNotFound()
raise
# Fetch list of changes.
changes = storage.list_all(
resource_name="record",
parent_id=collection_uri,
filters=filters,
limit=limit,
id_field="id",
modified_field="last_modified",
deleted_field="deleted",
sorting=[Sort("last_modified", -1)],
include_deleted=include_deleted,
)
# Fetch current collection timestamp.
records_timestamp = storage.resource_timestamp(
resource_name="record", parent_id=collection_uri
)
# We use the timestamp from the collection metadata, because we want it to
# be bumped when the signature is refreshed. Indeed, the CDN will revalidate
# the origin's response, only if the `Last-Modified` header has changed.
# Side note: We are sure that the collection timestamp is always higher
# than the records timestamp, because we have fields like `last_edit_date`
# in the collection metadata that are automatically bumped when records change.
last_modified = metadata["last_modified"]
# Do not serve inconsistent data.
if before != records_timestamp: # pragma: no cover
raise storage_exceptions.IntegrityError(message="Inconsistent data. Retry.")
# Cache control.
_handle_cache_expires(request, bid, cid)
# Set Last-Modified response header (Pyramid takes care of converting).
request.response.last_modified = last_modified / 1000.0
data = {
"metadata": {
**metadata,
"bucket": bid,
},
"timestamp": records_timestamp,
"changes": changes,
}
return data