api: Deprecate uri and add url parameter in "/user_uploads" endpoint.

This commit is contained in:
Vector73 2024-07-15 10:36:38 +05:30 committed by Tim Abbott
parent a07ebba860
commit d21ee6fa23
15 changed files with 78 additions and 38 deletions

View File

@ -20,6 +20,12 @@ format used by the Zulip server that they are interacting with.
## Changes in Zulip 9.0
**Feature level 272**
* [`POST /user_uploads`](/api/upload-file): `uri` was renamed
to `url`, but remains available as a deprecated alias for
backwards-compatibility.
**Feature level 271**
* [`GET /messages`](/api/get-messages),

View File

@ -34,7 +34,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
# new level means in api_docs/changelog.md, as well as "**Changes**"
# entries in the endpoint's documentation in `zulip.yaml`.
API_FEATURE_LEVEL = 271 # Last bumped for `with` operator.
API_FEATURE_LEVEL = 272 # Last bumped for "POST /user_uploads"
# Bump the minor PROVISION_VERSION to indicate that folks should provision

View File

@ -369,7 +369,7 @@ export function setup_upload(config: Config): Uppy {
uppy.on("upload-success", (file, response) => {
assert(file !== undefined);
const {uri: url} = z.object({uri: z.string().optional()}).parse(response.body);
const {url} = z.object({url: z.string().optional()}).parse(response.body);
if (url === undefined) {
return;
}

View File

@ -500,7 +500,7 @@ test("uppy_events", ({override_rewire, mock_template}) => {
};
let response = {
body: {
uri: "/user_uploads/4/cb/rue1c-MlMUjDAUdkRrEM4BTJ/copenhagen.png",
url: "/user_uploads/4/cb/rue1c-MlMUjDAUdkRrEM4BTJ/copenhagen.png",
},
};
@ -525,7 +525,7 @@ test("uppy_events", ({override_rewire, mock_template}) => {
response = {
body: {
uri: undefined,
url: undefined,
},
};
compose_ui_replace_syntax_called = false;

View File

@ -1416,7 +1416,7 @@ def upload_file(client: Client) -> None:
"type": "stream",
"to": "Denmark",
"topic": "Castle",
"content": "Check out [this picture]({}) of my castle!".format(result["uri"]),
"content": "Check out [this picture]({}) of my castle!".format(result["url"]),
}
)
# {code_example|end}

View File

@ -8093,7 +8093,7 @@ paths:
summary: Upload a file
tags: ["messages"]
description: |
Upload a single file and get the corresponding URI.
Upload a single file and get the corresponding URL.
Initially, only you will be able to access the link. To share the
uploaded file, you'll need to [send a message][send-message]
@ -8140,13 +8140,25 @@ paths:
ignored_parameters_unsupported: {}
uri:
type: string
deprecated: true
description: |
The URI of the uploaded file.
The URL of the uploaded file. Alias of `url`.
**Changes**: Deprecated in Zulip 9.0 (feature level 272). The term
"URI" is deprecated in [web standards](https://url.spec.whatwg.org/#goals).
url:
type: string
description: |
The URL of the uploaded file.
**Changes**: New in Zulip 9.0 (feature level 272). Previously,
this property was only available under the legacy `uri` name.
example:
{
"msg": "",
"result": "success",
"uri": "/user_uploads/1/4e/m2A3MSqFnWRLUf9SaPzQ0Up_/zulip.txt",
"url": "/user_uploads/1/4e/m2A3MSqFnWRLUf9SaPzQ0Up_/zulip.txt",
}
/user_uploads/{realm_id_str}/{filename}:
get:

View File

@ -32,7 +32,7 @@ class UnclaimedAttachmentTest(UploadSerializeMixin, ZulipTestCase):
response = self.assert_json_success(
self.client_post("/json/user_uploads", {"file": file_obj})
)
path_id = re.sub(r"/user_uploads/", "", response["uri"])
path_id = re.sub(r"/user_uploads/", "", response["url"])
return Attachment.objects.get(path_id=path_id)
def assert_exists(

View File

@ -3231,7 +3231,9 @@ class NormalActionsTest(BaseAction):
response_dict = self.assert_json_success(result)
self.assertIn("uri", response_dict)
url = response_dict["uri"]
self.assertIn("url", response_dict)
url = response_dict["url"]
self.assertEqual(response_dict["uri"], url)
base = "/user_uploads/"
self.assertEqual(base, url[: len(base)])

View File

@ -656,13 +656,13 @@ class ScheduledMessageTest(ZulipTestCase):
attachment_file1 = StringIO("zulip!")
attachment_file1.name = "dummy_1.txt"
result = self.client_post("/json/user_uploads", {"file": attachment_file1})
path_id1 = re.sub(r"/user_uploads/", "", result.json()["uri"])
path_id1 = re.sub(r"/user_uploads/", "", result.json()["url"])
attachment_object1 = Attachment.objects.get(path_id=path_id1)
attachment_file2 = StringIO("zulip!")
attachment_file2.name = "dummy_1.txt"
result = self.client_post("/json/user_uploads", {"file": attachment_file2})
path_id2 = re.sub(r"/user_uploads/", "", result.json()["uri"])
path_id2 = re.sub(r"/user_uploads/", "", result.json()["url"])
attachment_object2 = Attachment.objects.get(path_id=path_id2)
content = f"Test [zulip.txt](http://{hamlet.realm.host}/user_uploads/{path_id1})"

View File

@ -1195,7 +1195,7 @@ class StreamAdminTest(ZulipTestCase):
fp.name = "zulip.txt"
result = self.client_post("/json/user_uploads", {"file": fp})
url = self.assert_json_success(result)["uri"]
url = self.assert_json_success(result)["url"]
owner = self.example_user("desdemona")
realm = owner.realm

View File

@ -20,7 +20,9 @@ class ThumbnailTest(ZulipTestCase):
self.assert_json_success(result)
json = orjson.loads(result.content)
self.assertIn("uri", json)
url = json["uri"]
self.assertIn("url", json)
url = json["url"]
self.assertEqual(json["uri"], url)
base = "/user_uploads/"
self.assertEqual(base, url[: len(base)])
@ -60,7 +62,8 @@ class ThumbnailTest(ZulipTestCase):
result = self.client_post("/json/user_uploads", {"file": fp})
self.assert_json_success(result)
json = orjson.loads(result.content)
url = json["uri"]
url = json["url"]
self.assertEqual(json["uri"], url)
with ratelimit_rule(86400, 1000, domain="spectator_attachment_access_by_file"):
# Deny file access for non-web-public stream

View File

@ -56,7 +56,9 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
result = self.api_post(self.example_user("hamlet"), "/api/v1/user_uploads", {"file": fp})
response_dict = self.assert_json_success(result)
self.assertIn("uri", response_dict)
url = response_dict["uri"]
self.assertIn("url", response_dict)
url = response_dict["url"]
self.assertEqual(response_dict["uri"], url)
base = "/user_uploads/"
self.assertEqual(base, url[: len(base)])
@ -83,7 +85,9 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
result = self.api_post(self.example_user("hamlet"), "/api/v1/user_uploads", {"file": fp})
response_dict = self.assert_json_success(result)
self.assertIn("uri", response_dict)
url = response_dict["uri"]
self.assertIn("url", response_dict)
url = response_dict["url"]
self.assertEqual(response_dict["uri"], url)
base = "/user_uploads/"
self.assertEqual(base, url[: len(base)])
@ -171,7 +175,9 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
result = self.client_post("/json/user_uploads", {"file": fp})
response_dict = self.assert_json_success(result)
self.assertIn("uri", response_dict)
url = response_dict["uri"]
self.assertIn("url", response_dict)
url = response_dict["url"]
self.assertEqual(response_dict["uri"], url)
base = "/user_uploads/"
self.assertEqual(base, url[: len(base)])
@ -218,7 +224,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
fp.name = "zulip_web_public.txt"
result = self.client_post("/json/user_uploads", {"file": fp})
url = self.assert_json_success(result)["uri"]
url = self.assert_json_success(result)["url"]
with ratelimit_rule(86400, 1000, domain="spectator_attachment_access_by_file"):
# Deny file access for non-web-public stream
@ -275,7 +281,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
fp.name = "zulip.txt"
result = self.client_post("/json/user_uploads", {"file": fp})
response_dict = self.assert_json_success(result)
url = "/json" + response_dict["uri"]
url = "/json" + response_dict["url"]
result = self.client_get(url)
data = self.assert_json_success(result)
@ -292,7 +298,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
fp.name = "zulip.txt"
result = self.client_post("/json/user_uploads", {"file": fp})
response_dict = self.assert_json_success(result)
url = "/json" + response_dict["uri"]
url = "/json" + response_dict["url"]
start_time = time.time()
with mock.patch("django.core.signing.time.time", return_value=start_time):
@ -314,7 +320,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
fp.name = "zulip.txt"
result = self.client_post("/json/user_uploads", {"file": fp})
response_dict = self.assert_json_success(result)
url = response_dict["uri"]
url = response_dict["url"]
self.logout()
response = self.client_get(url)
@ -330,7 +336,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
fp.name = "zulip.txt"
result = self.client_post("/json/user_uploads", {"file": fp})
response_dict = self.assert_json_success(result)
url = response_dict["uri"]
url = response_dict["url"]
self.logout()
response = self.client_get(
@ -356,6 +362,8 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
response = self.client_get(response_dict["uri"])
self.assertEqual(response.status_code, 404)
response = self.client_get(response_dict["url"])
self.assertEqual(response.status_code, 404)
def test_non_existing_file_download(self) -> None:
"""
@ -421,7 +429,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
d1.name = "dummy_1.txt"
result = self.client_post("/json/user_uploads", {"file": d1})
response_dict = self.assert_json_success(result)
d1_path_id = re.sub(r"/user_uploads/", "", response_dict["uri"])
d1_path_id = re.sub(r"/user_uploads/", "", response_dict["url"])
self.subscribe(self.example_user("hamlet"), "Denmark")
host = self.example_user("hamlet").realm.host
@ -440,7 +448,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
d1.name = "dummy_1.txt"
result = self.client_post("/json/user_uploads", {"file": d1})
response_dict = self.assert_json_success(result)
d1_path_id = re.sub(r"/user_uploads/", "", response_dict["uri"])
d1_path_id = re.sub(r"/user_uploads/", "", response_dict["url"])
host = self.example_user("hamlet").realm.host
self.make_stream("private_stream", invite_only=True)
@ -500,11 +508,11 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
self.login_user(hamlet)
result = self.client_post("/json/user_uploads", {"file": f1})
response_dict = self.assert_json_success(result)
f1_path_id = re.sub(r"/user_uploads/", "", response_dict["uri"])
f1_path_id = re.sub(r"/user_uploads/", "", response_dict["url"])
result = self.client_post("/json/user_uploads", {"file": f2})
response_dict = self.assert_json_success(result)
f2_path_id = re.sub(r"/user_uploads/", "", response_dict["uri"])
f2_path_id = re.sub(r"/user_uploads/", "", response_dict["url"])
self.subscribe(hamlet, "test")
body = (
@ -515,7 +523,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
result = self.client_post("/json/user_uploads", {"file": f3})
response_dict = self.assert_json_success(result)
f3_path_id = re.sub(r"/user_uploads/", "", response_dict["uri"])
f3_path_id = re.sub(r"/user_uploads/", "", response_dict["url"])
new_body = (
f"[f3.txt](http://{host}/user_uploads/" + f3_path_id + ") "
@ -568,6 +576,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
result = self.client_post("/json/user_uploads", {"f1": fp})
response_dict = self.assert_json_success(result)
assert sanitize_name(expected) in response_dict["uri"]
assert sanitize_name(expected) in response_dict["url"]
def test_sanitize_file_name(self) -> None:
self.login("hamlet")
@ -587,6 +596,8 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
response_dict = self.assert_json_success(result)
self.assertNotIn(response_dict["uri"], uploaded_filename)
self.assertTrue(response_dict["uri"].endswith("/" + expected))
self.assertNotIn(response_dict["url"], uploaded_filename)
self.assertTrue(response_dict["url"].endswith("/" + expected))
def test_realm_quota(self) -> None:
"""
@ -598,7 +609,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
d1.name = "dummy_1.txt"
result = self.client_post("/json/user_uploads", {"file": d1})
response_dict = self.assert_json_success(result)
d1_path_id = re.sub(r"/user_uploads/", "", response_dict["uri"])
d1_path_id = re.sub(r"/user_uploads/", "", response_dict["url"])
d1_attachment = Attachment.objects.get(path_id=d1_path_id)
realm = get_realm("zulip")
@ -657,7 +668,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
fp = StringIO("zulip!")
fp.name = "zulip.txt"
result = self.client_post("/json/user_uploads", {"file": fp})
url = self.assert_json_success(result)["uri"]
url = self.assert_json_success(result)["url"]
fp_path_id = re.sub(r"/user_uploads/", "", url)
body = f"First message ...[zulip.txt](http://{host}/user_uploads/" + fp_path_id + ")"
with self.settings(CROSS_REALM_BOT_EMAILS={user_2.email, user_3.email}):
@ -703,7 +714,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
fp = StringIO("zulip!")
fp.name = "zulip.txt"
result = self.client_post("/json/user_uploads", {"file": fp})
url = self.assert_json_success(result)["uri"]
url = self.assert_json_success(result)["url"]
fp_path_id = re.sub(r"/user_uploads/", "", url)
body = f"First message ...[zulip.txt](http://{realm.host}/user_uploads/" + fp_path_id + ")"
self.send_stream_message(hamlet, stream_name, body, "test")
@ -755,7 +766,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
fp = StringIO("zulip!")
fp.name = "zulip.txt"
result = self.client_post("/json/user_uploads", {"file": fp})
url = self.assert_json_success(result)["uri"]
url = self.assert_json_success(result)["url"]
fp_path_id = re.sub(r"/user_uploads/", "", url)
body = (
f"First message ...[zulip.txt](http://{user.realm.host}/user_uploads/"
@ -824,7 +835,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
fp = StringIO("zulip!")
fp.name = "zulip.txt"
result = self.client_post("/json/user_uploads", {"file": fp})
url = self.assert_json_success(result)["uri"]
url = self.assert_json_success(result)["url"]
fp_path_id = re.sub(r"/user_uploads/", "", url)
for i in range(20):
body = (
@ -869,7 +880,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
fp = StringIO("zulip!")
fp.name = "zulip.txt"
result = self.client_post("/json/user_uploads", {"file": fp})
url = self.assert_json_success(result)["uri"]
url = self.assert_json_success(result)["url"]
fp_path_id = re.sub(r"/user_uploads/", "", url)
body = f"First message ...[zulip.txt](http://{realm.host}/user_uploads/" + fp_path_id + ")"
self.send_stream_message(self.example_user("hamlet"), "test-subscribe", body, "test")
@ -893,7 +904,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
fp = StringIO("zulip!")
fp.name = name
result = self.client_post("/json/user_uploads", {"file": fp})
url = self.assert_json_success(result)["uri"]
url = self.assert_json_success(result)["url"]
fp_path_id = re.sub(r"/user_uploads/", "", url)
fp_path = os.path.split(fp_path_id)[0]
if download:

View File

@ -76,7 +76,8 @@ class LocalStorageTest(UploadSerializeMixin, ZulipTestCase):
result = self.client_post("/json/user_uploads", {"file": fp})
response_dict = self.assert_json_success(result)
path_id = re.sub(r"/user_uploads/", "", response_dict["uri"])
self.assertEqual(response_dict["uri"], response_dict["url"])
path_id = re.sub(r"/user_uploads/", "", response_dict["url"])
assert settings.LOCAL_FILES_DIR is not None
file_path = os.path.join(settings.LOCAL_FILES_DIR, path_id)

View File

@ -164,8 +164,10 @@ class S3Test(ZulipTestCase):
result = self.client_post("/json/user_uploads", {"file": fp})
response_dict = self.assert_json_success(result)
self.assertIn("uri", response_dict)
self.assertIn("url", response_dict)
base = "/user_uploads/"
url = response_dict["uri"]
url = response_dict["url"]
self.assertEqual(response_dict["uri"], url)
self.assertEqual(base, url[: len(base)])
# In development, this is just a redirect

View File

@ -319,5 +319,8 @@ def upload_file_backend(request: HttpRequest, user_profile: UserProfile) -> Http
)
check_upload_within_quota(user_profile.realm, file_size)
uri = upload_message_attachment_from_request(user_file, user_profile)
return json_success(request, data={"uri": uri})
url = upload_message_attachment_from_request(user_file, user_profile)
# TODO/compatibility: uri is a deprecated alias for url that can
# be removed once there are no longer clients relying on it.
return json_success(request, data={"uri": url, "url": url})