diff --git a/api-template.yaml b/api-template.yaml index 06062c70..7f58d9fd 100644 --- a/api-template.yaml +++ b/api-template.yaml @@ -263,6 +263,11 @@ Resources: description: "ページ" required: false type: "integer" + - name: "offset" + in: "query" + description: "取得開始位置" + required: false + type: "integer" responses: "200": description: "検索記事一覧" @@ -298,6 +303,11 @@ Resources: description: "ページ" required: false type: "integer" + - name: "offset" + in: "query" + description: "取得開始位置" + required: false + type: "integer" responses: "200": description: "検索ユーザー一覧" @@ -333,6 +343,11 @@ Resources: description: "ページ数" required: false type: "integer" + - name: "offset" + in: "query" + description: "取得開始位置" + required: false + type: "integer" responses: "200": description: "最新記事一覧" @@ -360,7 +375,7 @@ Resources: minimum: 1 - name: 'page' in: 'query' - description: '取得対象ページのoffset' + description: 'ページ数' required: false type: 'integer' minimum: 1 @@ -369,6 +384,11 @@ Resources: description: '検索対象のトピック名' required: false type: 'string' + - name: "offset" + in: "query" + description: "取得開始位置" + required: false + type: "integer" responses: '200': description: '人気記事一覧' diff --git a/src/common/es_util.py b/src/common/es_util.py index 870c523c..16169680 100644 --- a/src/common/es_util.py +++ b/src/common/es_util.py @@ -4,7 +4,7 @@ class ESUtil: @staticmethod - def search_article(elasticsearch, word, limit, page): + def search_article(elasticsearch, word, limit, page, offset): body = { "query": { "bool": { @@ -16,7 +16,7 @@ def search_article(elasticsearch, word, limit, page): "_score", {"published_at": "desc"} ], - "from": limit*(page-1), + "from": limit*(page-1) + offset, "size": limit } for s in word.split(): @@ -44,7 +44,7 @@ def search_article(elasticsearch, word, limit, page): return res @staticmethod - def search_user(elasticsearch, word, limit, page): + def search_user(elasticsearch, word, limit, page, offset): body = { "query": { "bool": { @@ -54,7 +54,7 @@ def search_user(elasticsearch, word, limit, page): ] } }, - "from": limit*(page-1), + "from": limit*(page-1) + offset, "size": limit } res = elasticsearch.search( @@ -64,7 +64,7 @@ def search_user(elasticsearch, word, limit, page): return res @staticmethod - def search_popular_articles(elasticsearch, params, limit, page): + def search_popular_articles(elasticsearch, params, limit, page, offset): body = { 'query': { 'bool': { @@ -75,7 +75,7 @@ def search_popular_articles(elasticsearch, params, limit, page): 'sort': [ {'article_score': 'desc'} ], - 'from': limit * (page - 1), + 'from': limit * (page - 1) + offset, 'size': limit } @@ -92,7 +92,7 @@ def search_popular_articles(elasticsearch, params, limit, page): return articles @staticmethod - def search_recent_articles(elasticsearch, params, limit, page): + def search_recent_articles(elasticsearch, params, limit, page, offset): body = { 'query': { 'bool': { @@ -102,7 +102,7 @@ def search_recent_articles(elasticsearch, params, limit, page): 'sort': [ {'sort_key': 'desc'} ], - 'from': limit * (page - 1), + 'from': limit * (page - 1) + offset, 'size': limit } diff --git a/src/common/settings.py b/src/common/settings.py index 5332d7a0..f8064dad 100644 --- a/src/common/settings.py +++ b/src/common/settings.py @@ -91,6 +91,11 @@ 'minimum': 1, 'maximum': 100000 }, + 'offset': { + 'type': 'integer', + 'minimum': 0, + 'maximum': 100 + }, 'query': { 'type': 'string', 'minLength': 1, diff --git a/src/handlers/articles/popular/articles_popular.py b/src/handlers/articles/popular/articles_popular.py index 8be680cb..cfcc1f88 100644 --- a/src/handlers/articles/popular/articles_popular.py +++ b/src/handlers/articles/popular/articles_popular.py @@ -16,7 +16,8 @@ def get_schema(self): 'properties': { 'limit': settings.parameters['limit'], 'page': settings.parameters['page'], - 'topic': settings.parameters['topic'] + 'topic': settings.parameters['topic'], + 'offset': settings.parameters['offset'] } } @@ -31,8 +32,8 @@ def validate_params(self): def exec_main_proc(self): limit = int(self.params['limit']) if self.params.get('limit') else settings.articles_popular_default_limit page = int(self.params['page']) if self.params.get('page') else 1 - - articles = ESUtil.search_popular_articles(self.elasticsearch, self.params, limit, page) + offset = int(self.params['offset']) if self.params.get('offset') is not None else 0 + articles = ESUtil.search_popular_articles(self.elasticsearch, self.params, limit, page, offset) response = { 'Items': articles diff --git a/src/handlers/articles/recent/articles_recent.py b/src/handlers/articles/recent/articles_recent.py index cbe2f00c..6555c8f1 100644 --- a/src/handlers/articles/recent/articles_recent.py +++ b/src/handlers/articles/recent/articles_recent.py @@ -16,7 +16,8 @@ def get_schema(self): 'properties': { 'limit': settings.parameters['limit'], 'page': settings.parameters['page'], - 'topic': settings.parameters['topic'] + 'topic': settings.parameters['topic'], + 'offset': settings.parameters['offset'] } } @@ -32,8 +33,9 @@ def exec_main_proc(self): limit = int(self.params.get('limit')) if self.params.get('limit') is not None \ else settings.article_recent_default_limit page = int(self.params.get('page')) if self.params.get('page') is not None else 1 + offset = int(self.params['offset']) if self.params.get('offset') is not None else 0 - articles = ESUtil.search_recent_articles(self.elasticsearch, self.params, limit, page) + articles = ESUtil.search_recent_articles(self.elasticsearch, self.params, limit, page, offset) response = { 'Items': articles diff --git a/src/handlers/search/articles/search_articles.py b/src/handlers/search/articles/search_articles.py index c731dfa7..06c99287 100644 --- a/src/handlers/search/articles/search_articles.py +++ b/src/handlers/search/articles/search_articles.py @@ -15,7 +15,8 @@ def get_schema(self): 'properties': { 'limit': settings.parameters['limit'], 'page': settings.parameters['page'], - 'query': settings.parameters['query'] + 'query': settings.parameters['query'], + 'offset': settings.parameters['offset'] }, 'required': ['query'] } @@ -28,7 +29,8 @@ def exec_main_proc(self): query = self.params['query'] limit = int(self.params.get('limit')) if self.params.get('limit') is not None else settings.article_recent_default_limit page = int(self.params.get('page')) if self.params.get('page') is not None else 1 - response = ESUtil.search_article(self.elasticsearch, query, limit, page) + offset = int(self.params['offset']) if self.params.get('offset') is not None else 0 + response = ESUtil.search_article(self.elasticsearch, query, limit, page, offset) result = [] for a in response["hits"]["hits"]: del(a["_source"]["body"]) diff --git a/src/handlers/search/users/search_users.py b/src/handlers/search/users/search_users.py index 7a93d949..0f9c3478 100644 --- a/src/handlers/search/users/search_users.py +++ b/src/handlers/search/users/search_users.py @@ -15,7 +15,8 @@ def get_schema(self): 'properties': { 'limit': settings.parameters['limit'], 'page': settings.parameters['page'], - 'query': settings.parameters['query'] + 'query': settings.parameters['query'], + 'offset': settings.parameters['offset'] }, 'required': ['query'] } @@ -28,7 +29,8 @@ def exec_main_proc(self): query = self.params['query'] limit = int(self.params.get('limit')) if self.params.get('limit') is not None else settings.article_recent_default_limit page = int(self.params.get('page')) if self.params.get('page') is not None else 1 - response = ESUtil.search_user(self.elasticsearch, query, limit, page) + offset = int(self.params['offset']) if self.params.get('offset') is not None else 0 + response = ESUtil.search_user(self.elasticsearch, query, limit, page, offset) result = [] for u in response["hits"]["hits"]: result.append(u["_source"]) diff --git a/tests/handlers/articles/popular/test_articles_popular.py b/tests/handlers/articles/popular/test_articles_popular.py index ff07a3dd..25d00a66 100644 --- a/tests/handlers/articles/popular/test_articles_popular.py +++ b/tests/handlers/articles/popular/test_articles_popular.py @@ -219,6 +219,60 @@ def test_main_ok_with_page(self): self.assertEqual(response['statusCode'], 200) self.assertEqual(json.loads(response['body'])['Items'], expected_items) + def test_main_ok_with_offset(self): + params = { + 'queryStringParameters': { + 'limit': '1', + 'page': '1', + 'offset': '2' + } + } + + response = ArticlesPopular(params, {}, dynamodb=self.dynamodb, elasticsearch=self.elasticsearch).main() + + expected_items = [ + { + 'article_id': 'testid000003', + 'user_id': 'matsumatsu20', + 'created_at': 1520150272, + 'title': 'title04', + 'overview': 'overview04', + 'status': 'public', + 'topic': 'crypto', + 'article_score': 6, + 'sort_key': 1520150272000003 + } + ] + + self.assertEqual(response['statusCode'], 200) + self.assertEqual(json.loads(response['body'])['Items'], expected_items) + + def test_main_ok_with_only_offset(self): + params = { + 'queryStringParameters': { + 'offset': '2' + } + } + + response = ArticlesPopular(params, {}, dynamodb=self.dynamodb, elasticsearch=self.elasticsearch).main() + + expected_items = [ + { + 'article_id': 'testid000003', + 'user_id': 'matsumatsu20', + 'created_at': 1520150272, + 'title': 'title04', + 'overview': 'overview04', + 'status': 'public', + 'topic': 'crypto', + 'article_score': 6, + 'sort_key': 1520150272000003 + } + ] + + self.assertEqual(response['statusCode'], 200) + self.assertEqual(json.loads(response['body'])['Items'], expected_items) + def test_call_validate_topic(self): params = { 'queryStringParameters': { @@ -297,3 +351,30 @@ def test_validation_page_min(self): } self.assert_bad_request(params) + + def test_validation_offset_type(self): + params = { + 'queryStringParameters': { + 'offset': 'A' + } + } + + self.assert_bad_request(params) + + def test_validation_offset_max(self): + params = { + 'queryStringParameters': { + 'offset': '101' + } + } + + self.assert_bad_request(params) + + def test_validation_offset_min(self): + params = { + 'queryStringParameters': { + 'offset': '-1' + } + } + + self.assert_bad_request(params) diff --git a/tests/handlers/articles/recent/test_articles_recent.py b/tests/handlers/articles/recent/test_articles_recent.py index f8c290dd..ab6efe4f 100644 --- a/tests/handlers/articles/recent/test_articles_recent.py +++ b/tests/handlers/articles/recent/test_articles_recent.py @@ -163,6 +163,32 @@ def test_main_ok_with_page(self): self.assertEqual(response['statusCode'], 200) self.assertEqual(len(json.loads(response['body'])['Items']), 10) + def test_main_ok_with_offset(self): + params = { + 'queryStringParameters': { + 'limit': '20', + 'page': '2', + 'offset': '5', + 'topic': 'food' + } + } + response = ArticlesRecent(params, {}, dynamodb=self.dynamodb, elasticsearch=self.elasticsearch).main() + + self.assertEqual(response['statusCode'], 200) + self.assertEqual(len(json.loads(response['body'])['Items']), 5) + + def test_main_ok_with_only_offset(self): + params = { + 'queryStringParameters': { + 'offset': '28', + 'topic': 'food' + } + } + response = ArticlesRecent(params, {}, dynamodb=self.dynamodb, elasticsearch=self.elasticsearch).main() + + self.assertEqual(response['statusCode'], 200) + self.assertEqual(len(json.loads(response['body'])['Items']), 2) + def test_main_ok_exceed_page(self): params = { 'queryStringParameters': { @@ -245,3 +271,30 @@ def test_validation_too_big_page(self): } self.assert_bad_request(params) + + def test_validation_offset_type(self): + params = { + 'queryStringParameters': { + 'offset': 'A' + } + } + + self.assert_bad_request(params) + + def test_validation_offset_max(self): + params = { + 'queryStringParameters': { + 'offset': '101' + } + } + + self.assert_bad_request(params) + + def test_validation_offset_min(self): + params = { + 'queryStringParameters': { + 'offset': '-1' + } + } + + self.assert_bad_request(params) diff --git a/tests/handlers/search/articles/test_search_articles.py b/tests/handlers/search/articles/test_search_articles.py index bb185c11..402ed2a3 100644 --- a/tests/handlers/search/articles/test_search_articles.py +++ b/tests/handlers/search/articles/test_search_articles.py @@ -113,6 +113,32 @@ def test_search_request_page(self): response = SearchArticles(params, {}, elasticsearch=self.elasticsearch).main() self.assertEqual(response['statusCode'], 400) + def test_search_request_offset(self): + # offset 指定 + params = { + 'queryStringParameters': { + 'page': '3', + 'limit': '10', + 'offset': '5', + 'query': 'dummy' + } + } + response = SearchArticles(params, {}, elasticsearch=self.elasticsearch).main() + result = json.loads(response['body']) + self.assertRegex(result[0]['article_id'], '^dummy') + self.assertEqual(len(result), 5) + # offset 指定のみ + params = { + 'queryStringParameters': { + 'offset': '28', + 'query': 'dummy' + } + } + response = SearchArticles(params, {}, elasticsearch=self.elasticsearch).main() + result = json.loads(response['body']) + self.assertRegex(result[0]['article_id'], '^dummy') + self.assertEqual(len(result), 2) + def test_search_match_zero(self): params = { 'queryStringParameters': { @@ -133,3 +159,36 @@ def test_search_request_query_over150(self): } response = SearchArticles(params, {}, elasticsearch=self.elasticsearch).main() self.assertEqual(response['statusCode'], 400) + + def test_validation_offset_type(self): + params = { + 'queryStringParameters': { + 'query': 'dummy', + 'offset': 'A' + } + } + + response = SearchArticles(params, {}, elasticsearch=self.elasticsearch).main() + self.assertEqual(response['statusCode'], 400) + + def test_validation_offset_max(self): + params = { + 'queryStringParameters': { + 'query': 'dummy', + 'offset': '101' + } + } + + response = SearchArticles(params, {}, elasticsearch=self.elasticsearch).main() + self.assertEqual(response['statusCode'], 400) + + def test_validation_offset_min(self): + params = { + 'queryStringParameters': { + 'query': 'dummy', + 'offset': '-1' + } + } + + response = SearchArticles(params, {}, elasticsearch=self.elasticsearch).main() + self.assertEqual(response['statusCode'], 400) diff --git a/tests/handlers/search/users/test_search_users.py b/tests/handlers/search/users/test_search_users.py index dfc79dd0..b5640b22 100644 --- a/tests/handlers/search/users/test_search_users.py +++ b/tests/handlers/search/users/test_search_users.py @@ -94,6 +94,30 @@ def test_search_request_page(self): response = SearchUsers(params, {}, elasticsearch=self.elasticsearch).main() self.assertEqual(response['statusCode'], 400) + def test_search_request_offset(self): + # offset 指定 + params = { + 'queryStringParameters': { + 'limit': '10', + 'page': '3', + 'offset': '5', + 'query': 'testuser' + } + } + response = SearchUsers(params, {}, elasticsearch=self.elasticsearch).main() + result = json.loads(response['body']) + self.assertEqual(len(result), 5) + # offset 指定のみ + params = { + 'queryStringParameters': { + 'offset': '28', + 'query': 'testuser' + } + } + response = SearchUsers(params, {}, elasticsearch=self.elasticsearch).main() + result = json.loads(response['body']) + self.assertEqual(len(result), 2) + def test_search_match_zero(self): params = { 'queryStringParameters': { @@ -113,3 +137,36 @@ def test_search_request_query_over150(self): } response = SearchUsers(params, {}, elasticsearch=self.elasticsearch).main() self.assertEqual(response['statusCode'], 400) + + def test_validation_offset_type(self): + params = { + 'queryStringParameters': { + 'query': 'hogehoge', + 'offset': 'A' + } + } + + response = SearchUsers(params, {}, elasticsearch=self.elasticsearch).main() + self.assertEqual(response['statusCode'], 400) + + def test_validation_offset_max(self): + params = { + 'queryStringParameters': { + 'query': 'hogehoge', + 'offset': '101' + } + } + + response = SearchUsers(params, {}, elasticsearch=self.elasticsearch).main() + self.assertEqual(response['statusCode'], 400) + + def test_validation_offset_min(self): + params = { + 'queryStringParameters': { + 'query': 'hogehoge', + 'offset': '-1' + } + } + + response = SearchUsers(params, {}, elasticsearch=self.elasticsearch).main() + self.assertEqual(response['statusCode'], 400)