api: Add GET /users/{user_id}/subscription/{stream_id} endpoint.

This new endpoint returns a 'user' dictionary which, as of now,
contains a single key 'is_subscribed' with a boolean value that
represents whether the user with the given 'user_id' is subscribed
to the stream with the given 'stream_id'.

Fixes #14966.
This commit is contained in:
Kartik Srivastava 2020-05-31 22:40:41 +05:30 committed by Tim Abbott
parent d5cc29755e
commit 8c39ddfd28
9 changed files with 130 additions and 2 deletions

View File

@ -10,6 +10,11 @@ below features are supported.
## Changes in Zulip 2.2 ## Changes in Zulip 2.2
**Feature level 12**
* [`GET users/{user_id}/subscriptions/{stream_id}`](/api/get-subscription-status):
New endpoint added for checking if another user is subscribed to a stream.
**Feature level 11** **Feature level 11**
* [`POST /register`](/api/register-queue): Added * [`POST /register`](/api/register-queue): Added

View File

@ -0,0 +1,28 @@
# Get user subscription status
{generate_api_description(/users/{user_id}/subscriptions/{stream_id}:get)}
## Usage examples
{start_tabs}
{tab|python}
{generate_code_example(python)|/users/{user_id}/subscriptions/{stream_id}:get|example}
{tab|curl}
{generate_code_example(curl)|/users/{user_id}/subscriptions/{stream_id}:get|example}
{end_tabs}
## Arguments
{generate_api_arguments_table|zulip.yaml|/users/{user_id}/subscriptions/{stream_id}:get}
## Response
#### Example response
A typical successful JSON response may look like:
{generate_code_example|/users/{user_id}/subscriptions/{stream_id}:get|fixture(200)}

View File

@ -21,6 +21,7 @@
* [Add subscriptions](/api/add-subscriptions) * [Add subscriptions](/api/add-subscriptions)
* [Update subscription settings](/api/update-subscription-properties) * [Update subscription settings](/api/update-subscription-properties)
* [Remove subscriptions](/api/remove-subscriptions) * [Remove subscriptions](/api/remove-subscriptions)
* [Get subscription status](/api/get-subscription-status)
* [Get topics in a stream](/api/get-stream-topics) * [Get topics in a stream](/api/get-stream-topics)
* [Topic muting](/api/mute-topics) * [Topic muting](/api/mute-topics)
* [Create a stream](/api/create-stream) * [Create a stream](/api/create-stream)

View File

@ -29,7 +29,7 @@ DESKTOP_WARNING_VERSION = "5.2.0"
# #
# Changes should be accompanied by documentation explaining what the # Changes should be accompanied by documentation explaining what the
# new level means in templates/zerver/api/changelog.md. # new level means in templates/zerver/api/changelog.md.
API_FEATURE_LEVEL = 11 API_FEATURE_LEVEL = 12
# Bump the minor PROVISION_VERSION to indicate that folks should provision # Bump the minor PROVISION_VERSION to indicate that folks should provision
# only when going from an old version of the code to a newer version. Bump # only when going from an old version of the code to a newer version. Bump

View File

@ -253,6 +253,19 @@ def update_user(client: Client) -> None:
# {code_example|end} # {code_example|end}
validate_against_openapi_schema(result, '/users/{user_id}', 'patch', '400') validate_against_openapi_schema(result, '/users/{user_id}', 'patch', '400')
@openapi_test_function("/users/{user_id}/subscriptions/{stream_id}:get")
def get_subscription_status(client: Client) -> None:
# {code_example|start}
# Check whether a user is a subscriber to a given stream.
user_id = 7
stream_id = 1
result = client.call_endpoint(
url='/users/{}/subscriptions/{}'.format(user_id, stream_id),
method='GET',
)
# {code_example|end}
validate_against_openapi_schema(result, '/users/{user_id}/subscriptions/{stream_id}', 'get', '200')
@openapi_test_function("/realm/filters:get") @openapi_test_function("/realm/filters:get")
def get_realm_filters(client: Client) -> None: def get_realm_filters(client: Client) -> None:
@ -1117,6 +1130,7 @@ def test_users(client: Client) -> None:
deactivate_user(client) deactivate_user(client)
reactivate_user(client) reactivate_user(client)
update_user(client) update_user(client)
get_subscription_status(client)
get_profile(client) get_profile(client)
update_notification_settings(client) update_notification_settings(client)
upload_file(client) upload_file(client)

View File

@ -2628,6 +2628,40 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/NonExistingStreamError' $ref: '#/components/schemas/NonExistingStreamError'
/users/{user_id}/subscriptions/{stream_id}:
get:
operationId: get_subscription_status
tags: ["streams"]
description: |
Check whether a user is subscribed to a stream.
`GET {{ api_url }}/v1/users/{user_id}/subscriptions/{stream_id}`
**Changes**: New in Zulip 2.2 (feature level 11).
parameters:
- $ref: '#/components/parameters/UserId'
example: 7
- $ref: '#/components/parameters/StreamIdInPath'
example: 1
responses:
'200':
description: Success
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/JsonSuccess'
- properties:
is_subscribed:
type: boolean
description: |
Whether the user is subscribed to the stream.
- example:
{
"msg": "",
"result": "success",
"is_subscribed": false
}
/users/me/subscriptions/muted_topics: /users/me/subscriptions/muted_topics:
patch: patch:
operationId: mute_topic operationId: mute_topic

View File

@ -1093,6 +1093,38 @@ class UserProfileTest(ZulipTestCase):
get_user_by_id_in_realm_including_cross_realm( get_user_by_id_in_realm_including_cross_realm(
hamlet.id, None) hamlet.id, None)
def test_get_user_subscription_status(self) -> None:
self.login('hamlet')
iago = self.example_user('iago')
stream = get_stream('Rome', iago.realm)
# Invalid User ID.
result = self.client_get("/json/users/25/subscriptions/{}".format(stream.id))
self.assert_json_error(result, "No such user")
# Invalid Stream ID.
result = self.client_get("/json/users/{}/subscriptions/25".format(iago.id))
self.assert_json_error(result, "Invalid stream id")
result = ujson.loads(self.client_get("/json/users/{}/subscriptions/{}".format(iago.id, stream.id)).content)
self.assertFalse(result['is_subscribed'])
# Subscribe to the stream.
self.subscribe(iago, stream.name)
with queries_captured() as queries:
result = ujson.loads(self.client_get("/json/users/{}/subscriptions/{}".format(iago.id, stream.id)).content)
self.assert_length(queries, 7)
self.assertTrue(result['is_subscribed'])
# Logging in with a Guest user.
polonius = self.example_user("polonius")
self.login('polonius')
self.assertTrue(polonius.is_guest)
result = self.client_get("/json/users/{}/subscriptions/{}".format(iago.id, stream.id))
self.assert_json_error(result, "Invalid stream id")
class ActivateTest(ZulipTestCase): class ActivateTest(ZulipTestCase):
def test_basics(self) -> None: def test_basics(self) -> None:
user = self.example_user('hamlet') user = self.example_user('hamlet')

View File

@ -22,7 +22,7 @@ from zerver.lib.exceptions import CannotDeactivateLastUserError
from zerver.lib.integrations import EMBEDDED_BOTS from zerver.lib.integrations import EMBEDDED_BOTS
from zerver.lib.request import has_request_variables, REQ from zerver.lib.request import has_request_variables, REQ
from zerver.lib.response import json_error, json_success from zerver.lib.response import json_error, json_success
from zerver.lib.streams import access_stream_by_name from zerver.lib.streams import access_stream_by_name, access_stream_by_id, subscribed_to_stream
from zerver.lib.upload import upload_avatar_image from zerver.lib.upload import upload_avatar_image
from zerver.lib.validator import check_bool, check_string, check_int, check_url, check_dict, check_list, \ from zerver.lib.validator import check_bool, check_string, check_int, check_url, check_dict, check_list, \
check_int_in check_int_in
@ -471,3 +471,15 @@ def get_profile_backend(request: HttpRequest, user_profile: UserProfile) -> Http
result['max_message_id'] = messages[0].id result['max_message_id'] = messages[0].id
return json_success(result) return json_success(result)
@has_request_variables
def get_subscription_backend(request: HttpRequest, user_profile: UserProfile,
user_id: int=REQ(validator=check_int, path_only=True),
stream_id: int=REQ(validator=check_int, path_only=True)
) -> HttpResponse:
target_user = access_user_by_id(user_profile, user_id, read_only=True)
(stream, recipient, sub) = access_stream_by_id(user_profile, stream_id)
subscription_status = {'is_subscribed': subscribed_to_stream(target_user, stream_id)}
return json_success(subscription_status)

View File

@ -149,6 +149,8 @@ v1_api_and_json_patterns = [
{'GET': 'zerver.views.users.get_members_backend', {'GET': 'zerver.views.users.get_members_backend',
'PATCH': 'zerver.views.users.update_user_backend', 'PATCH': 'zerver.views.users.update_user_backend',
'DELETE': 'zerver.views.users.deactivate_user_backend'}), 'DELETE': 'zerver.views.users.deactivate_user_backend'}),
url(r'^users/(?P<user_id>[0-9]+)/subscriptions/(?P<stream_id>[0-9]+)$', rest_dispatch,
{'GET': 'zerver.views.users.get_subscription_backend'}),
url(r'^bots$', rest_dispatch, url(r'^bots$', rest_dispatch,
{'GET': 'zerver.views.users.get_bots_backend', {'GET': 'zerver.views.users.get_bots_backend',
'POST': 'zerver.views.users.add_bot_backend'}), 'POST': 'zerver.views.users.add_bot_backend'}),