diff --git a/API.md b/API.md index b301016..caa2e61 100644 --- a/API.md +++ b/API.md @@ -36,6 +36,7 @@ - ~~Scoring posts~~ - ~~Adding posts to favorites~~ - ~~Removing posts from favorites~~ + - [Getting featured post](#getting-featured-post) - [Featuring post](#featuring-post) - Users - [Listing users](#listing-users) @@ -48,6 +49,8 @@ - [Password reset - step 2: confirmation](#password-reset---step-2-confirmation) - Snapshots - [Listing snapshots](#listing-snapshots) + - Global info + - [Getting global info](#getting-global-info) 3. [Resources](#resources) @@ -601,6 +604,37 @@ data. list is truncated to the first 50 elements. Doesn't use paging. +## Getting featured post +- **Request** + + `GET /featured-post` + +- **Output** + + ```json5 + { + "post": , + "snapshots": [ + , + , + + ] + } + ``` + ...where `` is a [post resource](#post), and `snapshots` contain its + earlier versions. + +- **Errors** + + - privileges are too low + +- **Description** + + Retrieves the post that is currently featured on the main page in web + client. If no post is featured, `` is null and `snapshots` array is + empty. + + ## Featuring post - **Request** @@ -628,7 +662,7 @@ data. - **Description** - Features a post on the main page. + Features a post on the main page in web client. ## Listing users @@ -957,6 +991,27 @@ data. None. +## Getting global info +- **Request** + + `GET /info` + +- **Output** + + ```json5 + { + "postCount": , + "diskUsage": , // in bytes + "featuredPost": + } + ``` + +- **Description** + + Retrieves simple statistics. `` is null if there is no + featured post yet. + + # Resources diff --git a/server/szurubooru/api/info_api.py b/server/szurubooru/api/info_api.py index 2edcddb..cb9d011 100644 --- a/server/szurubooru/api/info_api.py +++ b/server/szurubooru/api/info_api.py @@ -1,6 +1,7 @@ import datetime import os from szurubooru import config +from szurubooru.api.post_api import serialize_post from szurubooru.api.base_api import BaseApi from szurubooru.func import posts @@ -10,10 +11,12 @@ class InfoApi(BaseApi): self._cache_time = None self._cache_result = None - def get(self, _ctx): + def get(self, ctx): + featured_post = posts.get_featured_post() return { 'postCount': posts.get_post_count(), - 'diskUsage': self._get_disk_usage() + 'diskUsage': self._get_disk_usage(), + 'featuredPost': serialize_post(featured_post, ctx.user), } def _get_disk_usage(self): diff --git a/server/szurubooru/api/post_api.py b/server/szurubooru/api/post_api.py index e38c84f..000bda1 100644 --- a/server/szurubooru/api/post_api.py +++ b/server/szurubooru/api/post_api.py @@ -3,6 +3,9 @@ from szurubooru.api.user_api import serialize_user from szurubooru.func import auth, posts, snapshots def serialize_post(post, authenticated_user): + if not post: + return None + ret = { 'id': post.post_id, 'creationTime': post.creation_time, @@ -56,3 +59,7 @@ class PostFeatureApi(BaseApi): snapshots.modify(post, ctx.user) ctx.session.commit() return serialize_post_with_details(post, ctx.user) + + def get(self, ctx): + post = posts.get_featured_post() + return serialize_post_with_details(post, ctx.user) diff --git a/server/szurubooru/api/snapshot_api.py b/server/szurubooru/api/snapshot_api.py index ec51de7..0f2d2bb 100644 --- a/server/szurubooru/api/snapshot_api.py +++ b/server/szurubooru/api/snapshot_api.py @@ -2,7 +2,7 @@ from szurubooru import search from szurubooru.api.base_api import BaseApi from szurubooru.func import auth, snapshots -def _serialize_snapshot(snapshot): +def serialize_snapshot(snapshot): earlier_snapshot = snapshots.get_previous_snapshot(snapshot) return snapshots.serialize_snapshot(snapshot, earlier_snapshot) @@ -14,4 +14,4 @@ class SnapshotListApi(BaseApi): def get(self, ctx): auth.verify_privilege(ctx.user, 'snapshots:list') return self._search_executor.execute_and_serialize( - ctx, _serialize_snapshot, 'snapshots') + ctx, serialize_snapshot, 'snapshots') diff --git a/server/szurubooru/api/tag_api.py b/server/szurubooru/api/tag_api.py index 98227bd..6dd54e4 100644 --- a/server/szurubooru/api/tag_api.py +++ b/server/szurubooru/api/tag_api.py @@ -3,7 +3,7 @@ from szurubooru import search from szurubooru.api.base_api import BaseApi from szurubooru.func import auth, tags, snapshots -def _serialize_tag(tag): +def serialize_tag(tag): return { 'names': [tag_name.name for tag_name in tag.names], 'category': tag.category.name, @@ -15,9 +15,9 @@ def _serialize_tag(tag): 'lastEditTime': tag.last_edit_time, } -def _serialize_tag_with_details(tag): +def serialize_tag_with_details(tag): return { - 'tag': _serialize_tag(tag), + 'tag': serialize_tag(tag), 'snapshots': snapshots.get_serialized_history(tag), } @@ -29,7 +29,7 @@ class TagListApi(BaseApi): def get(self, ctx): auth.verify_privilege(ctx.user, 'tags:list') return self._search_executor.execute_and_serialize( - ctx, _serialize_tag, 'tags') + ctx, serialize_tag, 'tags') def post(self, ctx): auth.verify_privilege(ctx.user, 'tags:create') @@ -47,7 +47,7 @@ class TagListApi(BaseApi): snapshots.create(tag, ctx.user) ctx.session.commit() tags.export_to_json() - return _serialize_tag_with_details(tag) + return serialize_tag_with_details(tag) class TagDetailApi(BaseApi): def get(self, ctx, tag_name): @@ -55,7 +55,7 @@ class TagDetailApi(BaseApi): tag = tags.get_tag_by_name(tag_name) if not tag: raise tags.TagNotFoundError('Tag %r not found.' % tag_name) - return _serialize_tag_with_details(tag) + return serialize_tag_with_details(tag) def put(self, ctx, tag_name): tag = tags.get_tag_by_name(tag_name) @@ -83,7 +83,7 @@ class TagDetailApi(BaseApi): snapshots.modify(tag, ctx.user) ctx.session.commit() tags.export_to_json() - return _serialize_tag_with_details(tag) + return serialize_tag_with_details(tag) def delete(self, ctx, tag_name): tag = tags.get_tag_by_name(tag_name) @@ -121,7 +121,7 @@ class TagMergeApi(BaseApi): tags.merge_tags(source_tag, target_tag) ctx.session.commit() tags.export_to_json() - return _serialize_tag_with_details(target_tag) + return serialize_tag_with_details(target_tag) class TagSiblingsApi(BaseApi): def get(self, ctx, tag_name): @@ -133,7 +133,7 @@ class TagSiblingsApi(BaseApi): serialized_siblings = [] for sibling, occurrences in result: serialized_siblings.append({ - 'tag': _serialize_tag(sibling), + 'tag': serialize_tag(sibling), 'occurrences': occurrences }) return {'siblings': serialized_siblings} diff --git a/server/szurubooru/api/tag_category_api.py b/server/szurubooru/api/tag_category_api.py index 19420a3..bb1170e 100644 --- a/server/szurubooru/api/tag_category_api.py +++ b/server/szurubooru/api/tag_category_api.py @@ -1,15 +1,15 @@ from szurubooru.api.base_api import BaseApi from szurubooru.func import auth, tags, tag_categories, snapshots -def _serialize_category(category): +def serialize_category(category): return { 'name': category.name, 'color': category.color, } -def _serialize_category_with_details(category): +def serialize_category_with_details(category): return { - 'tagCategory': _serialize_category(category), + 'tagCategory': serialize_category(category), 'snapshots': snapshots.get_serialized_history(category), } @@ -19,7 +19,7 @@ class TagCategoryListApi(BaseApi): categories = tag_categories.get_all_categories() return { 'tagCategories': [ - _serialize_category(category) for category in categories], + serialize_category(category) for category in categories], } def post(self, ctx): @@ -32,7 +32,7 @@ class TagCategoryListApi(BaseApi): snapshots.create(category, ctx.user) ctx.session.commit() tags.export_to_json() - return _serialize_category_with_details(category) + return serialize_category_with_details(category) class TagCategoryDetailApi(BaseApi): def get(self, ctx, category_name): @@ -41,7 +41,7 @@ class TagCategoryDetailApi(BaseApi): if not category: raise tag_categories.TagCategoryNotFoundError( 'Tag category %r not found.' % category_name) - return _serialize_category_with_details(category) + return serialize_category_with_details(category) def put(self, ctx, category_name): category = tag_categories.get_category_by_name(category_name) @@ -60,7 +60,7 @@ class TagCategoryDetailApi(BaseApi): snapshots.modify(category, ctx.user) ctx.session.commit() tags.export_to_json() - return _serialize_category_with_details(category) + return serialize_category_with_details(category) def delete(self, ctx, category_name): category = tag_categories.get_category_by_name(category_name) diff --git a/server/szurubooru/func/snapshots.py b/server/szurubooru/func/snapshots.py index bbb13d8..eaf4100 100644 --- a/server/szurubooru/func/snapshots.py +++ b/server/szurubooru/func/snapshots.py @@ -91,6 +91,8 @@ def serialize_snapshot(snapshot, earlier_snapshot): } def get_serialized_history(entity): + if not entity: + return [] ret = [] earlier_snapshot = None for snapshot in reversed(get_snapshots(entity)): diff --git a/server/szurubooru/tests/api/test_info.py b/server/szurubooru/tests/api/test_info.py index e9d2c1a..561f212 100644 --- a/server/szurubooru/tests/api/test_info.py +++ b/server/szurubooru/tests/api/test_info.py @@ -11,15 +11,18 @@ def test_info_api( assert info_api.get(context_factory()) == { 'postCount': 2, 'diskUsage': 3, + 'featuredPost': None, } directory.join('test2.txt').write('abc') with fake_datetime('13:59'): assert info_api.get(context_factory()) == { 'postCount': 2, 'diskUsage': 3, # still 3 - it's cached + 'featuredPost': None, } with fake_datetime('14:01'): assert info_api.get(context_factory()) == { 'postCount': 2, 'diskUsage': 6, # cache expired + 'featuredPost': None, } diff --git a/server/szurubooru/tests/api/test_post_featuring.py b/server/szurubooru/tests/api/test_post_featuring.py index 1172fc2..9bb751b 100644 --- a/server/szurubooru/tests/api/test_post_featuring.py +++ b/server/szurubooru/tests/api/test_post_featuring.py @@ -6,7 +6,10 @@ from szurubooru.func import util, posts @pytest.fixture def test_ctx(context_factory, config_injector, user_factory, post_factory): config_injector({ - 'privileges': {'posts:feature': 'regular_user'}, + 'privileges': { + 'posts:feature': 'regular_user', + 'posts:view': 'regular_user', + }, 'ranks': ['anonymous', 'regular_user'], }) ret = util.dotdict() @@ -16,10 +19,16 @@ def test_ctx(context_factory, config_injector, user_factory, post_factory): ret.api = api.PostFeatureApi() return ret +def test_no_featured_post(test_ctx): + assert posts.get_featured_post() is None + result = test_ctx.api.get( + test_ctx.context_factory( + user=test_ctx.user_factory(rank='regular_user'))) + assert result == {'post': None, 'snapshots': []} + def test_featuring(test_ctx): db.session.add(test_ctx.post_factory(id=1)) db.session.commit() - assert posts.get_featured_post() is None assert not posts.get_post_by_id(1).is_featured result = test_ctx.api.post( test_ctx.context_factory( @@ -31,6 +40,11 @@ def test_featuring(test_ctx): assert 'post' in result assert 'snapshots' in result assert 'id' in result['post'] + result = test_ctx.api.get( + test_ctx.context_factory( + user=test_ctx.user_factory(rank='regular_user'))) + assert 'post' in result + assert 'id' in result['post'] def test_trying_to_feature_the_same_post_twice(test_ctx): db.session.add(test_ctx.post_factory(id=1)) @@ -80,3 +94,11 @@ def test_trying_to_feature_without_privileges(test_ctx): test_ctx.context_factory( input={'id': 1}, user=test_ctx.user_factory(rank='anonymous'))) + +def test_getting_featured_post_without_privileges_to_view(test_ctx): + try: + test_ctx.api.get( + test_ctx.context_factory( + user=test_ctx.user_factory(rank='anonymous'))) + except: + pytest.fail()