diff --git a/API.md b/API.md index 4dd6d72..fe097cc 100644 --- a/API.md +++ b/API.md @@ -39,7 +39,7 @@ - [Getting featured post](#getting-featured-post) - [Featuring post](#featuring-post) - Comments - - ~~Listing comments~~ + - [Listing comments](#listing-comments) - [Creating comment](#creating-comment) - [Updating comment](#updating-comment) - [Getting comment](#getting-comment) @@ -673,6 +673,75 @@ data. Features a post on the main page in web client. +## Listing comments +- **Request** + + `GET /comments/?page=&pageSize=&query=` + +- **Output** + + ```json5 + { + "query": , // same as in input + "page": , // same as in input + "pageSize": , + "total": , + "comments": [ + , + , + + ] + } + ``` + ...where `` is a [comment resource](#comment) and `query` contains + standard [search query](#search). + +- **Errors** + + - privileges are too low + +- **Description** + + Searches for comments. + + **Anonymous tokens** + + Same as `text` token. + + **Named tokens** + + | `` | Description | + | ---------------- | ---------------------------------------------- | + | `id` | specific comment ID | + | `post` | specific post ID | + | `user` | created by given user (accepts wildcards) | + | `text` | containing given text (accepts wildcards) | + | `creation-date` | created at given date | + | `creation-time` | alias of `creation-date` | + | `last-edit-date` | whose most recent edit date matches given date | + | `last-edit-time` | alias of `last-edit-date` | + | `edit-date` | alias of `last-edit-date` | + | `edit-time` | alias of `last-edit-date` | + + **Sort style tokens** + + | `` | Description | + | ---------------- | ------------------------- | + | `random` | as random as it can get | + | `user` | author name, A to Z | + | `post` | post ID, newest to oldest | + | `creation-date` | newest to oldest | + | `creation-time` | alias of `creation-date` | + | `last-edit-date` | recently edited first | + | `last-edit-time` | alias of `last-edit-date` | + | `edit-date` | alias of `last-edit-date` | + | `edit-time` | alias of `last-edit-date` | + + **Special tokens** + + None. + + ## Creating comment - **Request** diff --git a/server/szurubooru/api/comment_api.py b/server/szurubooru/api/comment_api.py index 7b26712..d1201ff 100644 --- a/server/szurubooru/api/comment_api.py +++ b/server/szurubooru/api/comment_api.py @@ -1,10 +1,20 @@ import datetime +from szurubooru import search from szurubooru.api.base_api import BaseApi from szurubooru.func import auth, comments, posts class CommentListApi(BaseApi): + def __init__(self): + super().__init__() + self._search_executor = search.SearchExecutor( + search.CommentSearchConfig()) + def get(self, ctx): - raise NotImplementedError() + auth.verify_privilege(ctx.user, 'comments:list') + return self._search_executor.execute_and_serialize( + ctx, + lambda comment: comments.serialize_comment(comment, ctx.user), + 'comments') def post(self, ctx): auth.verify_privilege(ctx.user, 'comments:create') diff --git a/server/szurubooru/search/__init__.py b/server/szurubooru/search/__init__.py index a6c6e7f..14db2bc 100644 --- a/server/szurubooru/search/__init__.py +++ b/server/szurubooru/search/__init__.py @@ -4,3 +4,4 @@ from szurubooru.search.search_executor import SearchExecutor from szurubooru.search.user_search_config import UserSearchConfig from szurubooru.search.snapshot_search_config import SnapshotSearchConfig from szurubooru.search.tag_search_config import TagSearchConfig +from szurubooru.search.comment_search_config import CommentSearchConfig diff --git a/server/szurubooru/search/comment_search_config.py b/server/szurubooru/search/comment_search_config.py new file mode 100644 index 0000000..c01979d --- /dev/null +++ b/server/szurubooru/search/comment_search_config.py @@ -0,0 +1,43 @@ +from sqlalchemy.sql.expression import func +from szurubooru import db +from szurubooru.search.base_search_config import BaseSearchConfig + +class CommentSearchConfig(BaseSearchConfig): + def create_query(self): + return db.session.query(db.Comment).join(db.User) + + def finalize_query(self, query): + return query.order_by(db.Comment.creation_time.desc()) + + @property + def anonymous_filter(self): + return self._create_str_filter(db.Comment.text) + + @property + def named_filters(self): + return { + 'id': self._create_num_filter(db.Comment.comment_id), + 'post': self._create_num_filter(db.Comment.post_id), + 'user': self._create_str_filter(db.User.name), + 'text': self._create_str_filter(db.Comment.text), + 'creation-date': self._create_date_filter(db.Comment.creation_time), + 'creation-time': self._create_date_filter(db.Comment.creation_time), + 'last-edit-date': self._create_date_filter(db.Comment.last_edit_time), + 'last-edit-time': self._create_date_filter(db.Comment.last_edit_time), + 'edit-date': self._create_date_filter(db.Comment.last_edit_time), + 'edit-time': self._create_date_filter(db.Comment.last_edit_time), + } + + @property + def sort_columns(self): + return { + 'random': (func.random(), None), + 'user': (db.User.name, self.SORT_ASC), + 'post': (db.Comment.post_id, self.SORT_DESC), + 'creation-date': (db.Comment.creation_time, self.SORT_DESC), + 'creation-time': (db.Comment.creation_time, self.SORT_DESC), + 'last-edit-date': (db.Comment.last_edit_time, self.SORT_DESC), + 'last-edit-time': (db.Comment.last_edit_time, self.SORT_DESC), + 'edit-date': (db.Comment.last_edit_time, self.SORT_DESC), + 'edit-time': (db.Comment.last_edit_time, self.SORT_DESC), + } diff --git a/server/szurubooru/search/snapshot_search_config.py b/server/szurubooru/search/snapshot_search_config.py index 4136766..daa783d 100644 --- a/server/szurubooru/search/snapshot_search_config.py +++ b/server/szurubooru/search/snapshot_search_config.py @@ -14,7 +14,7 @@ class SnapshotSearchConfig(BaseSearchConfig): 'type': self._create_str_filter(db.Snapshot.resource_type), 'id': self._create_str_filter(db.Snapshot.resource_repr), 'date': self._create_date_filter(db.Snapshot.creation_time), - 'time': self._create_str_filter(db.Snapshot.creation_time), + 'time': self._create_date_filter(db.Snapshot.creation_time), 'operation': self._create_str_filter(db.Snapshot.operation), 'user': self._create_str_filter(db.User.name), } diff --git a/server/szurubooru/tests/api/test_comment_retrieving.py b/server/szurubooru/tests/api/test_comment_retrieving.py index 03c7e46..1358081 100644 --- a/server/szurubooru/tests/api/test_comment_retrieving.py +++ b/server/szurubooru/tests/api/test_comment_retrieving.py @@ -18,9 +18,31 @@ def test_ctx(context_factory, config_injector, user_factory, comment_factory): ret.context_factory = context_factory ret.user_factory = user_factory ret.comment_factory = comment_factory + ret.list_api = api.CommentListApi() ret.detail_api = api.CommentDetailApi() return ret +def test_retrieving_multiple(test_ctx): + comment1 = test_ctx.comment_factory(text='text 1') + comment2 = test_ctx.comment_factory(text='text 2') + db.session.add_all([comment1, comment2]) + result = test_ctx.list_api.get( + test_ctx.context_factory( + input={'query': '', 'page': 1}, + user=test_ctx.user_factory(rank='regular_user'))) + assert result['query'] == '' + assert result['page'] == 1 + assert result['pageSize'] == 100 + assert result['total'] == 2 + assert [c['text'] for c in result['comments']] == ['text 1', 'text 2'] + +def test_trying_to_retrieve_multiple_without_privileges(test_ctx): + with pytest.raises(errors.AuthError): + test_ctx.list_api.get( + test_ctx.context_factory( + input={'query': '', 'page': 1}, + user=test_ctx.user_factory(rank='anonymous'))) + def test_retrieving_single(test_ctx): comment = test_ctx.comment_factory(text='dummy text') db.session.add(comment) diff --git a/server/szurubooru/tests/search/test_comment_search_config.py b/server/szurubooru/tests/search/test_comment_search_config.py new file mode 100644 index 0000000..33e6510 --- /dev/null +++ b/server/szurubooru/tests/search/test_comment_search_config.py @@ -0,0 +1,131 @@ +import datetime +import pytest +from szurubooru import db, errors, search + +@pytest.fixture +def executor(): + search_config = search.CommentSearchConfig() + return search.SearchExecutor(search_config) + +@pytest.fixture +def verify_unpaged(executor): + def verify(input, expected_comment_text): + actual_count, actual_comments = executor.execute( + input, page=1, page_size=100) + print(actual_comments) + actual_comment_text = [c.text for c in actual_comments] + assert actual_count == len(expected_comment_text) + assert actual_comment_text == expected_comment_text + return verify + +@pytest.mark.parametrize('input,expected_comment_text', [ + ('creation-time:2014', ['t2', 't1']), + ('creation-date:2014', ['t2', 't1']), +]) +def test_filter_by_creation_time( + verify_unpaged, comment_factory, input, expected_comment_text): + comment1 = comment_factory(text='t1') + comment2 = comment_factory(text='t2') + comment3 = comment_factory(text='t3') + comment1.creation_time = datetime.datetime(2014, 1, 1) + comment2.creation_time = datetime.datetime(2014, 6, 1) + comment3.creation_time = datetime.datetime(2015, 1, 1) + db.session.add_all([comment1, comment2, comment3]) + verify_unpaged(input, expected_comment_text) + +@pytest.mark.parametrize('input,expected_comment_text', [ + ('text:t1', ['t1']), + ('text:t2', ['t2']), + ('text:t1,t2', ['t1', 't2']), + ('text:t*', ['t1', 't2']), +]) +def test_filter_by_text( + verify_unpaged, comment_factory, input, expected_comment_text): + comment1 = comment_factory(text='t1') + comment2 = comment_factory(text='t2') + db.session.add_all([comment1, comment2]) + verify_unpaged(input, expected_comment_text) + +@pytest.mark.parametrize('input,expected_comment_text', [ + ('user:u1', ['t1']), + ('user:u2', ['t2']), + ('user:u1,u2', ['t2', 't1']), +]) +def test_filter_by_user( + verify_unpaged, comment_factory, user_factory, input, expected_comment_text): + db.session.add(comment_factory(text='t2', user=user_factory(name='u2'))) + db.session.add(comment_factory(text='t1', user=user_factory(name='u1'))) + verify_unpaged(input, expected_comment_text) + +@pytest.mark.parametrize('input,expected_comment_text', [ + ('post:1', ['t1']), + ('post:2', ['t2']), + ('post:1,2', ['t1', 't2']), +]) +def test_filter_by_post( + verify_unpaged, comment_factory, post_factory, input, expected_comment_text): + db.session.add(comment_factory(text='t1', post=post_factory(id=1))) + db.session.add(comment_factory(text='t2', post=post_factory(id=2))) + verify_unpaged(input, expected_comment_text) + +@pytest.mark.parametrize('input,expected_comment_text', [ + ('', ['t1', 't2']), + ('t1', ['t1']), + ('t2', ['t2']), + ('t1,t2', ['t1', 't2']), +]) +def test_anonymous(verify_unpaged, comment_factory, input, expected_comment_text): + db.session.add(comment_factory(text='t1')) + db.session.add(comment_factory(text='t2')) + verify_unpaged(input, expected_comment_text) + +@pytest.mark.parametrize('input,expected_comment_text', [ + ('sort:user', ['t1', 't2']), +]) +def test_sort_by_user( + verify_unpaged, comment_factory, user_factory, input, expected_comment_text): + db.session.add(comment_factory(text='t2', user=user_factory(name='u2'))) + db.session.add(comment_factory(text='t1', user=user_factory(name='u1'))) + verify_unpaged(input, expected_comment_text) + +@pytest.mark.parametrize('input,expected_comment_text', [ + ('sort:post', ['t2', 't1']), +]) +def test_sort_by_post( + verify_unpaged, comment_factory, post_factory, input, expected_comment_text): + db.session.add(comment_factory(text='t1', post=post_factory(id=1))) + db.session.add(comment_factory(text='t2', post=post_factory(id=2))) + verify_unpaged(input, expected_comment_text) + +@pytest.mark.parametrize('input,expected_comment_text', [ + ('', ['t3', 't2', 't1']), + ('sort:creation-date', ['t3', 't2', 't1']), + ('sort:creation-time', ['t3', 't2', 't1']), +]) +def test_sort_by_creation_time( + verify_unpaged, comment_factory, input, expected_comment_text): + comment1 = comment_factory(text='t1') + comment2 = comment_factory(text='t2') + comment3 = comment_factory(text='t3') + comment1.creation_time = datetime.datetime(1991, 1, 1) + comment2.creation_time = datetime.datetime(1991, 1, 2) + comment3.creation_time = datetime.datetime(1991, 1, 3) + db.session.add_all([comment3, comment1, comment2]) + verify_unpaged(input, expected_comment_text) + +@pytest.mark.parametrize('input,expected_comment_text', [ + ('sort:last-edit-date', ['t3', 't2', 't1']), + ('sort:last-edit-time', ['t3', 't2', 't1']), + ('sort:edit-date', ['t3', 't2', 't1']), + ('sort:edit-time', ['t3', 't2', 't1']), +]) +def test_sort_by_last_edit_time( + verify_unpaged, comment_factory, input, expected_comment_text): + comment1 = comment_factory(text='t1') + comment2 = comment_factory(text='t2') + comment3 = comment_factory(text='t3') + comment1.last_edit_time = datetime.datetime(1991, 1, 1) + comment2.last_edit_time = datetime.datetime(1991, 1, 2) + comment3.last_edit_time = datetime.datetime(1991, 1, 3) + db.session.add_all([comment3, comment1, comment2]) + verify_unpaged(input, expected_comment_text) diff --git a/server/szurubooru/tests/search/test_tag_search_config.py b/server/szurubooru/tests/search/test_tag_search_config.py index 61755fb..62e64b1 100644 --- a/server/szurubooru/tests/search/test_tag_search_config.py +++ b/server/szurubooru/tests/search/test_tag_search_config.py @@ -102,13 +102,13 @@ def test_sort_by_name(verify_unpaged, tag_factory, input, expected_tag_names): db.session.add(tag_factory(names=['t1'])) verify_unpaged(input, expected_tag_names) -@pytest.mark.parametrize('input,expected_user_names', [ +@pytest.mark.parametrize('input,expected_tag_names', [ ('', ['t1', 't2', 't3']), ('sort:creation-date', ['t3', 't2', 't1']), ('sort:creation-time', ['t3', 't2', 't1']), ]) def test_sort_by_creation_time( - verify_unpaged, tag_factory, input, expected_user_names): + verify_unpaged, tag_factory, input, expected_tag_names): tag1 = tag_factory(names=['t1']) tag2 = tag_factory(names=['t2']) tag3 = tag_factory(names=['t3']) @@ -116,7 +116,7 @@ def test_sort_by_creation_time( tag2.creation_time = datetime.datetime(1991, 1, 2) tag3.creation_time = datetime.datetime(1991, 1, 3) db.session.add_all([tag3, tag1, tag2]) - verify_unpaged(input, expected_user_names) + verify_unpaged(input, expected_tag_names) @pytest.mark.parametrize('input,expected_tag_names', [ ('sort:suggestion-count', ['t1', 't2', 'sug1', 'sug2', 'sug3']),