server/comments: add comment search
This commit is contained in:
parent
b75cfff8f7
commit
0b47957bb9
8 changed files with 282 additions and 6 deletions
71
API.md
71
API.md
|
@ -39,7 +39,7 @@
|
||||||
- [Getting featured post](#getting-featured-post)
|
- [Getting featured post](#getting-featured-post)
|
||||||
- [Featuring post](#featuring-post)
|
- [Featuring post](#featuring-post)
|
||||||
- Comments
|
- Comments
|
||||||
- ~~Listing comments~~
|
- [Listing comments](#listing-comments)
|
||||||
- [Creating comment](#creating-comment)
|
- [Creating comment](#creating-comment)
|
||||||
- [Updating comment](#updating-comment)
|
- [Updating comment](#updating-comment)
|
||||||
- [Getting comment](#getting-comment)
|
- [Getting comment](#getting-comment)
|
||||||
|
@ -673,6 +673,75 @@ data.
|
||||||
Features a post on the main page in web client.
|
Features a post on the main page in web client.
|
||||||
|
|
||||||
|
|
||||||
|
## Listing comments
|
||||||
|
- **Request**
|
||||||
|
|
||||||
|
`GET /comments/?page=<page>&pageSize=<page-size>&query=<query>`
|
||||||
|
|
||||||
|
- **Output**
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
"query": <query>, // same as in input
|
||||||
|
"page": <page>, // same as in input
|
||||||
|
"pageSize": <page-size>,
|
||||||
|
"total": <total-count>,
|
||||||
|
"comments": [
|
||||||
|
<comment>,
|
||||||
|
<comment>,
|
||||||
|
<comment>
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
...where `<comment>` 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**
|
||||||
|
|
||||||
|
| `<value>` | 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**
|
||||||
|
|
||||||
|
| `<value>` | 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
|
## Creating comment
|
||||||
- **Request**
|
- **Request**
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,20 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
from szurubooru import search
|
||||||
from szurubooru.api.base_api import BaseApi
|
from szurubooru.api.base_api import BaseApi
|
||||||
from szurubooru.func import auth, comments, posts
|
from szurubooru.func import auth, comments, posts
|
||||||
|
|
||||||
class CommentListApi(BaseApi):
|
class CommentListApi(BaseApi):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._search_executor = search.SearchExecutor(
|
||||||
|
search.CommentSearchConfig())
|
||||||
|
|
||||||
def get(self, ctx):
|
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):
|
def post(self, ctx):
|
||||||
auth.verify_privilege(ctx.user, 'comments:create')
|
auth.verify_privilege(ctx.user, 'comments:create')
|
||||||
|
|
|
@ -4,3 +4,4 @@ from szurubooru.search.search_executor import SearchExecutor
|
||||||
from szurubooru.search.user_search_config import UserSearchConfig
|
from szurubooru.search.user_search_config import UserSearchConfig
|
||||||
from szurubooru.search.snapshot_search_config import SnapshotSearchConfig
|
from szurubooru.search.snapshot_search_config import SnapshotSearchConfig
|
||||||
from szurubooru.search.tag_search_config import TagSearchConfig
|
from szurubooru.search.tag_search_config import TagSearchConfig
|
||||||
|
from szurubooru.search.comment_search_config import CommentSearchConfig
|
||||||
|
|
43
server/szurubooru/search/comment_search_config.py
Normal file
43
server/szurubooru/search/comment_search_config.py
Normal file
|
@ -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),
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ class SnapshotSearchConfig(BaseSearchConfig):
|
||||||
'type': self._create_str_filter(db.Snapshot.resource_type),
|
'type': self._create_str_filter(db.Snapshot.resource_type),
|
||||||
'id': self._create_str_filter(db.Snapshot.resource_repr),
|
'id': self._create_str_filter(db.Snapshot.resource_repr),
|
||||||
'date': self._create_date_filter(db.Snapshot.creation_time),
|
'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),
|
'operation': self._create_str_filter(db.Snapshot.operation),
|
||||||
'user': self._create_str_filter(db.User.name),
|
'user': self._create_str_filter(db.User.name),
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,31 @@ def test_ctx(context_factory, config_injector, user_factory, comment_factory):
|
||||||
ret.context_factory = context_factory
|
ret.context_factory = context_factory
|
||||||
ret.user_factory = user_factory
|
ret.user_factory = user_factory
|
||||||
ret.comment_factory = comment_factory
|
ret.comment_factory = comment_factory
|
||||||
|
ret.list_api = api.CommentListApi()
|
||||||
ret.detail_api = api.CommentDetailApi()
|
ret.detail_api = api.CommentDetailApi()
|
||||||
return ret
|
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):
|
def test_retrieving_single(test_ctx):
|
||||||
comment = test_ctx.comment_factory(text='dummy text')
|
comment = test_ctx.comment_factory(text='dummy text')
|
||||||
db.session.add(comment)
|
db.session.add(comment)
|
||||||
|
|
131
server/szurubooru/tests/search/test_comment_search_config.py
Normal file
131
server/szurubooru/tests/search/test_comment_search_config.py
Normal file
|
@ -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)
|
|
@ -102,13 +102,13 @@ def test_sort_by_name(verify_unpaged, tag_factory, input, expected_tag_names):
|
||||||
db.session.add(tag_factory(names=['t1']))
|
db.session.add(tag_factory(names=['t1']))
|
||||||
verify_unpaged(input, expected_tag_names)
|
verify_unpaged(input, expected_tag_names)
|
||||||
|
|
||||||
@pytest.mark.parametrize('input,expected_user_names', [
|
@pytest.mark.parametrize('input,expected_tag_names', [
|
||||||
('', ['t1', 't2', 't3']),
|
('', ['t1', 't2', 't3']),
|
||||||
('sort:creation-date', ['t3', 't2', 't1']),
|
('sort:creation-date', ['t3', 't2', 't1']),
|
||||||
('sort:creation-time', ['t3', 't2', 't1']),
|
('sort:creation-time', ['t3', 't2', 't1']),
|
||||||
])
|
])
|
||||||
def test_sort_by_creation_time(
|
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'])
|
tag1 = tag_factory(names=['t1'])
|
||||||
tag2 = tag_factory(names=['t2'])
|
tag2 = tag_factory(names=['t2'])
|
||||||
tag3 = tag_factory(names=['t3'])
|
tag3 = tag_factory(names=['t3'])
|
||||||
|
@ -116,7 +116,7 @@ def test_sort_by_creation_time(
|
||||||
tag2.creation_time = datetime.datetime(1991, 1, 2)
|
tag2.creation_time = datetime.datetime(1991, 1, 2)
|
||||||
tag3.creation_time = datetime.datetime(1991, 1, 3)
|
tag3.creation_time = datetime.datetime(1991, 1, 3)
|
||||||
db.session.add_all([tag3, tag1, tag2])
|
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', [
|
@pytest.mark.parametrize('input,expected_tag_names', [
|
||||||
('sort:suggestion-count', ['t1', 't2', 'sug1', 'sug2', 'sug3']),
|
('sort:suggestion-count', ['t1', 't2', 'sug1', 'sug2', 'sug3']),
|
||||||
|
|
Loading…
Reference in a new issue