Exemple #1
0
def delete_forum(id: int) -> flask.Response:
    """
    This is the endpoint for forum deletion . The ``forums_forums_modify`` permission
    is required to access this endpoint. All threads in a deleted forum will also
    be deleted.

    .. :quickref: Forum; Delete a forum.

    **Example response**:

    .. parsed-literal::

       {
         "status": "success",
         "response": "Forum 1 (Pulsar) has been deleted."
       }

    :>json str response: The deleted forum message

    :statuscode 200: Deletion successful
    :statuscode 400: Deletion unsuccessful
    :statuscode 404: Forum does not exist
    """
    forum = Forum.from_pk(id, _404=True)
    forum.deleted = True
    ForumThread.update_many(pks=ForumThread.get_ids_from_forum(forum.id),
                            update={'deleted': True})
    return flask.jsonify(f'Forum {id} ({forum.name}) has been deleted.')
Exemple #2
0
def test_thread_cache(app, authed_client):
    thread = ForumThread.from_pk(1)
    cache.cache_model(thread, timeout=60)
    thread = ForumThread.from_pk(1)
    assert thread.id == 1
    assert thread.topic == 'New Site'
    assert cache.ttl(thread.cache_key) < 61
Exemple #3
0
def test_new_thread_failure(app, authed_client, forum_id, creator_id):
    with pytest.raises(APIException):
        ForumThread.new(
            topic='NewForumThread',
            forum_id=forum_id,
            creator_id=creator_id,
            post_contents='a',
        )
Exemple #4
0
def test_thread_can_access_explicit_disallow(app, authed_client):
    db.engine.execute(
        "DELETE FROM users_permissions WHERE permission LIKE 'forumaccess%%'")
    add_permissions(app, 'forumaccess_forum_1')
    db.session.execute(
        """INSERT INTO users_permissions (user_id, permission, granted)
                       VALUES (1, 'forumaccess_thread_1', 'f')""")
    with pytest.raises(_403Exception):
        ForumThread.from_pk(1, error=True)
def test_delete_forum(app, authed_client):
    add_permissions(app, 'forums_view', 'forums_forums_modify')
    sub_thread = ForumThread.from_pk(
        5)  # Cache - thread isn't deleted, belongs to category
    response = authed_client.delete('/forums/5')
    check_json_response(response, 'Forum 5 (Yacht Funding) has been deleted.')
    forum = ForumThread.from_pk(5, include_dead=True)
    assert forum.deleted
    sub_thread = ForumThread.from_pk(5, include_dead=True)
    assert sub_thread.deleted
Exemple #6
0
def test_new_thread(app, authed_client):
    thread = ForumThread.new(topic='NewForumThread',
                             forum_id=2,
                             creator_id=1,
                             post_contents='aaaa')
    assert thread.topic == 'NewForumThread'
    assert thread.forum_id == 2
    assert thread.creator_id == 1
    assert ForumThread.from_cache(thread.cache_key).id == thread.id == 6
    assert len(thread.posts) == 1
    assert thread.posts[0].contents == 'aaaa'
Exemple #7
0
def test_thread_get_from_forum_cached(app, authed_client):
    cache.set(ForumThread.__cache_key_of_forum__.format(id=2), [1, 5],
              timeout=60)
    ForumThread.from_pk(1)
    ForumThread.from_pk(5)  # noqa cache these
    threads = ForumThread.from_forum(2, page=1, limit=50)
    assert len(threads) == 2

    for thread in threads:
        if thread.topic == 'Donations?' and thread.id == 5:
            break
    else:
        raise AssertionError('A real thread not called')
Exemple #8
0
def test_subscribe_thread_deletes_cache_keys(app, authed_client):
    add_permissions(app, ForumPermissions.MODIFY_SUBSCRIPTIONS)
    ForumThread.from_subscribed_user(1)
    ForumThreadSubscription.user_ids_from_thread(5)
    response = authed_client.post('/subscriptions/threads/5')
    assert response.status_code == 200
    assert not cache.get(
        ForumThreadSubscription.__cache_key_users__.format(thread_id=5)
    )
    assert not cache.get(
        ForumThreadSubscription.__cache_key_of_user__.format(user_id=1)
    )
    assert ForumThreadSubscription.user_ids_from_thread(5) == [1]
def test_add_thread_forum_subscriptions(app, authed_client):
    add_permissions(app, 'forums_view', 'forums_threads_create')
    ForumThread.from_subscribed_user(user_id=1)  # cache
    response = authed_client.post(
        '/forums/threads',
        data=json.dumps(
            {'topic': 'New Thread', 'forum_id': 4, 'contents': 'aa'}
        ),
    )
    check_json_response(response, {'id': 6})
    assert response.get_json()['response']['forum']['id'] == 4
    assert {1, 2} == set(ForumThreadSubscription.user_ids_from_thread(6))
    assert 6 in {t.id for t in ForumThread.from_subscribed_user(user_id=1)}
Exemple #10
0
def view_thread_subscriptions() -> flask.Response:
    """
    This is the endpoint to view forum and thread subscriptions. The
    ``forums_view_subscriptions`` permission is required to access this endpoint.

    .. :quickref: ForumSubscription; View forum subscriptions.

    **Example response**:

    .. parsed-literal::

       {
         "status": "success",
         "response": [
              "<ForumThread>",
              "<ForumThread>"
            ]
          }
        }

    :>json dict response: The forum and thread subscriptions

    :statuscode 200: The forum subscriptions
    """
    return flask.jsonify(ForumThread.from_subscribed_user(flask.g.user.id))
Exemple #11
0
def test_thread_can_access_implicit_forum(app, authed_client):
    db.engine.execute(
        "DELETE FROM users_permissions WHERE permission LIKE 'forumaccess%%'")
    add_permissions(app, 'forumaccess_forum_1')
    thread = ForumThread.from_pk(1)
    assert thread.id == 1
    assert thread.topic == 'New Site'
Exemple #12
0
def test_serialize_very_detailed(app, authed_client):
    add_permissions(app, 'forums_threads_modify',
                    'forums_threads_modify_advanced')
    thread = ForumThread.from_pk(3)
    data = NewJSONEncoder().default(thread)
    check_dictionary(
        data,
        {
            'id': 3,
            'topic': 'Using PHP',
            'locked': True,
            'sticky': True,
            'deleted': False,
            'post_count': 1,
            'subscribed': True,
        },
    )
    assert 'forum' in data and data['forum']['id'] == 2
    assert 'creator' in data and data['creator']['id'] == 2
    assert 'last_post' in data and data['last_post']['id'] == 2
    assert 'last_viewed_post' in data and data['last_viewed_post']['id'] == 2
    assert ('posts' in data and len(data['posts']) == 1
            and data['posts'][0]['id'] == 2)
    assert 'created_time' in data and isinstance(data['created_time'], int)
    assert 'poll' in data and data['poll']['id'] == 3
    assert 'thread_notes' in data
    assert ('thread_notes' in data and len(data['thread_notes']) == 1
            and data['thread_notes'][0]['id'] == 3)
Exemple #13
0
def test_thread_last_viewed_post_deleted(app, authed_client):
    thread = ForumThread.from_pk(5)
    last_post = thread.last_viewed_post
    assert last_post.id == 3
    assert last_post.thread_id == 5
    assert 3 == cache.get(
        ForumLastViewedPost.__cache_key__.format(thread_id=5, user_id=1))
Exemple #14
0
def test_thread_last_post_already_cached(app, authed_client):
    thread = ForumThread.from_pk(2, include_dead=True)
    post = ForumPost.from_pk(6)
    cache.cache_model(post, timeout=60)
    post = thread.last_post
    assert post.contents == 'Delete this'
    assert cache.ttl(post.cache_key) < 61
Exemple #15
0
def modify_post(id: int,
                sticky: bool = None,
                contents: str = None) -> flask.Response:
    """
    This is the endpoint for forum post editing. The ``forums_posts_modify``
    permission is required to access this endpoint. Posts can be marked
    sticky with this endpoint.

    .. :quickref: ForumThread; Edit a forum post.

    **Example request**:

    .. parsed-literal::

       PUT /forums/posts/6 HTTP/1.1

       {
         "sticky": true
       }


    **Example response**:

    .. parsed-literal::

       {
         "status": "success",
         "response": "<ForumPost>"
       }

    :>json dict response: The modified forum post

    :statuscode 200: Modification successful
    :statuscode 400: Modification unsuccessful
    :statuscode 404: Forum post does not exist
    """
    post = ForumPost.from_pk(id, _404=True)
    assert_user(post.user_id, 'forums_posts_modify')
    thread = ForumThread.from_pk(post.thread_id)
    if not thread:
        raise APIException(f'ForumPost {id} does not exist.')
    if thread.locked and not flask.g.user.has_permission(
            'forums_posts_modify'):
        raise APIException('You cannot modify posts in a locked thread.')
    if contents is not None:
        ForumPostEditHistory.new(
            post_id=post.id,
            editor_id=post.edited_user_id or post.user_id,
            contents=post.contents,
            time=datetime.utcnow().replace(tzinfo=pytz.utc),
        )
        post.contents = contents
        post.edited_user_id = flask.g.user.id
        post.edited_time = datetime.utcnow().replace(tzinfo=pytz.utc)
    if flask.g.user.has_permission('forums_posts_modify'):
        if sticky is not None:
            post.sticky = sticky
    db.session.commit()
    return flask.jsonify(post)
Exemple #16
0
def test_thread_last_viewed_post_cached(app, authed_client):
    cache.set(ForumLastViewedPost.__cache_key__.format(thread_id=1, user_id=1),
              2)
    thread = ForumThread.from_pk(1)
    last_post = thread.last_viewed_post
    assert last_post.id == 2
    assert last_post.thread_id == 3
    assert 2 == cache.get(
        ForumLastViewedPost.__cache_key__.format(thread_id=1, user_id=1))
Exemple #17
0
def test_thread_get_from_forum(app, authed_client):
    threads = ForumThread.from_forum(1, page=1, limit=50)
    assert len(threads) == 1

    for thread in threads:
        if thread.topic == 'New Site' and thread.id == 1:
            break
    else:
        raise AssertionError('A real thread not called')
Exemple #18
0
def alter_thread_subscription(thread_id: int) -> flask.Response:
    """
    This is the endpoint for forum thread subscription. The ``forums_subscriptions_modify``
    permission is required to access this endpoint. A POST request creates a subscription,
    whereas a DELETE request removes a subscription.

    .. :quickref: ForumThreadSubscription; Subscribe to a forum thread.

    **Example response**:

    .. parsed-literal::

       {
         "status": "success",
         "response": "Successfully subscribed to thread 2."
       }

    :>json str response: Success or failure message

    :statuscode 200: Subscription alteration successful
    :statuscode 400: Subscription alteration unsuccessful
    :statuscode 404: Forum thread does not exist
    """
    thread = ForumThread.from_pk(thread_id, _404=True)
    subscription = ForumThreadSubscription.from_attrs(
        user_id=flask.g.user.id, thread_id=thread.id
    )
    if flask.request.method == 'POST':
        if subscription:
            raise APIException(
                f'You are already subscribed to thread {thread_id}.'
            )
        ForumThreadSubscription.new(
            user_id=flask.g.user.id, thread_id=thread_id
        )
        return flask.jsonify(f'Successfully subscribed to thread {thread_id}.')
    else:  # method = DELETE
        if not subscription:
            raise APIException(
                f'You are not subscribed to thread {thread_id}.'
            )
        db.session.delete(subscription)
        db.session.commit()
        cache.delete(
            ForumThreadSubscription.__cache_key_users__.format(
                thread_id=thread_id
            )
        )
        cache.delete(
            ForumThreadSubscription.__cache_key_of_user__.format(
                user_id=flask.g.user.id
            )
        )
        return flask.jsonify(
            f'Successfully unsubscribed from thread {thread_id}.'
        )
def test_edit_thread_skips(app, authed_client):
    add_permissions(app, 'forums_view', 'forums_threads_modify')
    response = authed_client.put(
        '/forums/threads/1', data=json.dumps({'sticky': True})
    )
    check_json_response(
        response,
        {'id': 1, 'topic': 'New Site', 'locked': False, 'sticky': True},
    )
    thread = ForumThread.from_pk(1)
    assert thread.sticky is True
Exemple #20
0
def create_post(contents: str, thread_id: int) -> flask.Response:
    """
    This is the endpoint for forum posting. The ``forums_posts_modify``
    permission is required to access this endpoint.

    .. :quickref: ForumPost; Create a forum post.

    **Example request**:

    .. parsed-literal::

       POST /forums/posts HTTP/1.1

       {
         "topic": "How do I get easy ration?",
         "forum_id": 4
       }

    **Example response**:

    .. parsed-literal::

       {
         "status": "success",
         "response": "<ForumPost>"
       }

    :>json dict response: The newly created forum post

    :statuscode 200: Creation successful
    :statuscode 400: Creation unsuccessful
    """
    thread = ForumThread.from_pk(thread_id, _404=True)
    if thread.locked and not flask.g.user.has_permission(
            'forums_post_in_locked'):
        raise APIException('You cannot post in a locked thread.')
    thread_last_post = thread.last_post
    if (thread_last_post and thread_last_post.user_id == flask.g.user.id
            and not flask.g.user.has_permission('forums_posts_double')):
        if len(thread_last_post.contents) + len(contents) > 255997:
            raise APIException('Post could not be merged into previous post '
                               '(must be <256,000 characters combined).')
        return modify_post(
            id=thread_last_post.id,
            contents=f'{thread_last_post.contents}\n\n\n{contents}',
            skip_validation=True,
        )

    post = ForumPost.new(thread_id=thread_id,
                         user_id=flask.g.user.id,
                         contents=contents)
    return flask.jsonify(post)
Exemple #21
0
def view_thread(id: int,
                page: int = 1,
                limit: int = 50,
                include_dead: bool = False) -> flask.Response:
    """
    This endpoint allows users to view details about a forum and its threads.

    .. :quickref: ForumThread; View a forum thread.

    **Example request**:

    .. parsed-literal::

       GET /forums/threads/1 HTTP/1.1
       Host: pul.sar
       Accept: application/json

    **Example response**:

    .. parsed-literal::

       HTTP/1.1 200 OK
       Vary: Accept
       Content-Type: application/json

       {
         "status": "success",
         "response": [
           {
           }
         ]
       }

    :>json list response: A forum thread

    :statuscode 200: View successful
    :statuscode 403: User does not have permission to view thread
    :statuscode 404: Thread does not exist
    """
    thread = ForumThread.from_pk(
        id,
        _404=True,
        include_dead=flask.g.user.has_permission(
            'forums_threads_modify_advanced'),
    )
    thread.set_posts(
        page,
        limit,
        include_dead
        and flask.g.user.has_permission('forums_posts_modify_advanced'),
    )
    return flask.jsonify(thread)
def test_delete_thread(app, authed_client):
    add_permissions(app, 'forums_view', 'forums_threads_modify_advanced')
    post = ForumPost.from_pk(
        3
    )  # Cache - post isn't deleted, belongs to thread
    response = authed_client.delete('/forums/threads/5')
    check_json_response(
        response, 'ForumThread 5 (Donations?) has been deleted.'
    )
    thread = ForumThread.from_pk(5, include_dead=True)
    assert thread.deleted
    post = ForumPost.from_pk(3, include_dead=True)
    assert post.deleted
def test_edit_thread(app, authed_client):
    add_permissions(app, 'forums_view', 'forums_threads_modify')
    response = authed_client.put(
        '/forums/threads/1',
        data=json.dumps({'topic': 'Bite', 'forum_id': 4, 'locked': True}),
    )
    check_json_response(
        response, {'id': 1, 'topic': 'Bite', 'locked': True, 'sticky': False}
    )
    assert response.get_json()['response']['forum']['id'] == 4
    thread = ForumThread.from_pk(1)
    assert thread.id == 1
    assert thread.topic == 'Bite'
    assert thread.locked is True
    assert thread.sticky is False
Exemple #24
0
def create_thread(topic: str, forum_id: int, contents: str) -> flask.Response:
    """
    This is the endpoint for forum thread creation. The ``forums_threads_modify``
    permission is required to access this endpoint.

    .. :quickref: ForumThread; Create a forum thread.

    **Example request**:

    .. parsed-literal::

       POST /forums/threads HTTP/1.1
       Host: pul.sar
       Accept: application/json
       Content-Type: application/json

       {
         "topic": "How do I get easy ration?",
         "forum_id": 4
       }

    **Example response**:

    .. parsed-literal::

       HTTP/1.1 200 OK
       Vary: Accept
       Content-Type: application/json

       {
         "status": "success",
         "response": {
         }
       }

    :>json dict response: The newly created forum thread

    :statuscode 200: Creation successful
    :statuscode 400: Creation unsuccessful
    """
    return flask.jsonify(
        ForumThread.new(
            topic=topic,
            forum_id=forum_id,
            creator_id=flask.g.user.id,
            post_contents=contents,
        ))
Exemple #25
0
def delete_thread(id: int) -> flask.Response:
    """
    This is the endpoint for forum thread deletion . The ``forums_threads_modify_advanced``
    permission is required to access this endpoint. All posts in a deleted forum will also
    be deleted.

    .. :quickref: ForumThread; Delete a forum thread.

    **Example request**:

    .. parsed-literal::

       DELETE /forums/threads/2 HTTP/1.1
       Host: pul.sar
       Accept: application/json
       Content-Type: application/json

    **Example response**:

    .. parsed-literal::

       HTTP/1.1 200 OK
       Vary: Accept
       Content-Type: application/json

       {
         "status": "success",
         "response": {
         }
       }

    :>json dict response: The deleted forum thread

    :statuscode 200: Deletion successful
    :statuscode 400: Deletion unsuccessful
    :statuscode 404: Forum thread does not exist
    """
    thread = ForumThread.from_pk(id, _404=True)
    thread.deleted = True
    ForumPost.update_many(pks=ForumPost.get_ids_from_thread(thread.id),
                          update={'deleted': True})
    return flask.jsonify(
        f'ForumThread {id} ({thread.topic}) has been deleted.')
Exemple #26
0
def test_serialize_nested(app, authed_client):
    add_permissions(app, 'forums_threads_modify_advanced')
    thread = ForumThread.from_pk(3)
    data = NewJSONEncoder()._objects_to_dict(thread.serialize(nested=True))
    check_dictionary(
        data,
        {
            'id': 3,
            'topic': 'Using PHP',
            'locked': True,
            'sticky': True,
            'deleted': False,
            'post_count': 1,
            'subscribed': True,
        },
    )
    assert 'creator' in data and data['creator']['id'] == 2
    assert 'last_post' in data and data['last_post']['id'] == 2
    assert 'last_viewed_post' in data and data['last_viewed_post']['id'] == 2
    assert 'created_time' in data and isinstance(data['created_time'], int)
Exemple #27
0
def test_serialize_no_perms(app, authed_client):
    thread = ForumThread.from_pk(3)
    data = NewJSONEncoder().default(thread)
    check_dictionary(
        data,
        {
            'id': 3,
            'topic': 'Using PHP',
            'locked': True,
            'sticky': True,
            'post_count': 1,
            'subscribed': True,
        },
    )
    assert 'forum' in data and data['forum']['id'] == 2
    assert 'creator' in data and data['creator']['id'] == 2
    assert 'last_post' in data and data['last_post']['id'] == 2
    assert 'last_viewed_post' in data and data['last_viewed_post']['id'] == 2
    assert ('posts' in data and len(data['posts']) == 1
            and data['posts'][0]['id'] == 2)
    assert 'created_time' in data and isinstance(data['created_time'], int)
    assert 'poll' in data and data['poll']['id'] == 3
Exemple #28
0
def modify_thread(
    id: int,
    topic: str = None,
    forum_id: int = None,
    locked: bool = None,
    sticky: bool = None,
) -> flask.Response:
    """
    This is the endpoint for forum thread editing. The ``forums_threads_modify``
    permission is required to access this endpoint. The topic, forum_id,
    locked, and sticky attributes can be changed here.

    .. :quickref: ForumThread; Edit a forum thread.

    **Example request**:

    .. parsed-literal::

       PUT /forums/threads/6 HTTP/1.1
       Host: pul.sar
       Accept: application/json
       Content-Type: application/json

       {
         "topic": "This does not contain typos.",
         "forum_id": 2,
         "locked": true,
         "sticky": false
       }


    **Example response**:

    .. parsed-literal::

       HTTP/1.1 200 OK
       Vary: Accept
       Content-Type: application/json

       {
         "status": "success",
         "response": {
         }
       }

    :>json dict response: The edited forum thread

    :statuscode 200: Editing successful
    :statuscode 400: Editing unsuccessful
    :statuscode 404: Forum thread does not exist
    """
    thread = ForumThread.from_pk(id, _404=True)
    if topic:
        thread.topic = topic
    if forum_id and Forum.is_valid(forum_id, error=True):
        thread.forum_id = forum_id
    if locked is not None:
        thread.locked = locked
    if sticky is not None:
        thread.sticky = sticky
    db.session.commit()
    return flask.jsonify(thread)
def test_delete_thread_no_posts(app, authed_client):
    add_permissions(app, 'forums_view', 'forums_threads_modify_advanced')
    response = authed_client.delete('/forums/threads/1')
    check_json_response(response, 'ForumThread 1 (New Site) has been deleted.')
    thread = ForumThread.from_pk(1, include_dead=True)
    assert thread.deleted
def test_subscribe_users_to_new_thread(app, authed_client):
    thread = ForumThread.new(topic='aa',
                             forum_id=5,
                             creator_id=1,
                             post_contents='hello')
    assert ForumThreadSubscription.user_ids_from_thread(thread.id) == [3, 4]