diff --git a/client/html/banned_post_entry.tpl b/client/html/banned_post_entry.tpl index defc6a92..87ec0f2a 100644 --- a/client/html/banned_post_entry.tpl +++ b/client/html/banned_post_entry.tpl @@ -1,9 +1,9 @@ -<% + <%- ctx.postBan.checksum %> - <%- ctx.makeRelativeTime(ctx.postBan.time) %> + <%= ctx.makeRelativeTime(ctx.postBan.time) %> <% if (ctx.canDelete) { %> diff --git a/client/js/controllers/top_navigation_controller.js b/client/js/controllers/top_navigation_controller.js index fbc5399d..da90959a 100644 --- a/client/js/controllers/top_navigation_controller.js +++ b/client/js/controllers/top_navigation_controller.js @@ -53,6 +53,9 @@ class TopNavigationController { if (!api.hasPrivilege("pools:list")) { topNavigation.hide("pools"); } + if (!api.hasPrivilege("posts:ban:list")) { + topNavigation.hide("banned-posts"); + } if (api.isLoggedIn()) { if (!api.hasPrivilege("users:create:any")) { topNavigation.hide("register"); diff --git a/client/js/models/banned_post_list.js b/client/js/models/banned_post_list.js index 2665b4c1..61ac3a27 100644 --- a/client/js/models/banned_post_list.js +++ b/client/js/models/banned_post_list.js @@ -26,8 +26,8 @@ class BannedPostList extends AbstractList { save() { let promises = []; - for (let BannedPost of this._deletedBans) { - promises.push(BannedPost.delete()); + for (let bannedPost of this._deletedBans) { + promises.push(bannedPost.delete()); } return Promise.all(promises).then((response) => { @@ -37,7 +37,7 @@ class BannedPostList extends AbstractList { } _evtBannedPostDeleted(e) { - this._deletedBans.push(e.detail.BannedPost); + this._deletedBans.push(e.detail.bannedPost); } } diff --git a/client/js/models/post.js b/client/js/models/post.js index 15fb61ea..58374173 100644 --- a/client/js/models/post.js +++ b/client/js/models/post.js @@ -336,7 +336,8 @@ class Post extends events.EventTarget { ban() { return api - .post(uri.formatApiLink("post-ban", this.id), { + .post(uri.formatApiLink("post-ban"), { + post_id: this.id, version: this._version }) .then((response) => { diff --git a/client/js/models/top_navigation.js b/client/js/models/top_navigation.js index a469a034..3a680010 100644 --- a/client/js/models/top_navigation.js +++ b/client/js/models/top_navigation.js @@ -90,6 +90,7 @@ function _makeTopNavigation() { ret.add("login", new TopNavigationItem("L", "Log in", "login")); ret.add("logout", new TopNavigationItem("O", "Logout", "logout")); ret.add("help", new TopNavigationItem("E", "Help", "help")); + ret.add("banned-posts", new TopNavigationItem("B", "Banned posts", "banned-posts")); ret.add( "settings", new TopNavigationItem(null, "", "settings") diff --git a/client/js/views/banned_posts_view.js b/client/js/views/banned_posts_view.js index 89fe3b4b..0a29d955 100644 --- a/client/js/views/banned_posts_view.js +++ b/client/js/views/banned_posts_view.js @@ -83,7 +83,7 @@ class BannedPostsView extends events.EventTarget { } _evtBannedPostDeleted(e) { - this._removeBannedPostRowNode(e.detail.poolCategory); + this._removeBannedPostRowNode(e.detail.bannedPost); } _evtDeleteButtonClick(e, rowNode, link) { diff --git a/doc/API.md b/doc/API.md index 6287f61d..e388cb7a 100644 --- a/doc/API.md +++ b/doc/API.md @@ -912,7 +912,7 @@ data. - **Output** - A [post resource](#post). + A [post resource](#banned-post). - **Errors** @@ -1006,15 +1006,35 @@ data. Deletes existing post. Related posts and tags are kept. + +## Listing banned posts +- **Request** + + `GET /post-ban` + +- **Output** + + An [unpaged search result](#unpaged-search-result) of [banned posts](#postban). + +- **Errors** + + - the post does not exist + - privileges are too low + +- **Description** + + Retrieves information about an existing post. + ## Banning post - **Request** - `POST /post-ban/` + `POST /post-ban` - **Input** ```json5 { + "post_id": "version": } ``` @@ -1039,6 +1059,27 @@ data. Related posts and tags are kept. +## Undoing post ban +- **Request** + + `DELETE /post-ban/` + +- **Output** + + ```json5 + {} + ``` + +- **Errors** + + - there is no banned image with that hash + - privileges are too low + +- **Description** + + Removes a banned image from the ban list. Takes a SHA-1 hash of the image as input. + + ## Merging posts - **Request** @@ -2617,6 +2658,26 @@ An ordered list of posts, with a description and category. A [pool resource](#pool) stripped down to `id`, `names`, `category`, `description` and `postCount` fields. + +## Banned post +**Description** + +A record of a post that has been banned. + +**Structure** + +```json5 +{ + "checksum": , + "time": +} +``` + +**Field meaning** +- ``: SHA-1 hash of an image that has been banned +- ``: time the post was banned + + ## Comment **Description** diff --git a/server/szurubooru/api/ban_api.py b/server/szurubooru/api/ban_api.py index b6238815..10b0f956 100644 --- a/server/szurubooru/api/ban_api.py +++ b/server/szurubooru/api/ban_api.py @@ -1,8 +1,8 @@ from datetime import datetime from typing import Dict, List, Optional -from server.szurubooru.api import post_api -from server.szurubooru.func import posts -from server.szurubooru.model.bans import PostBan +from szurubooru.api import post_api +from szurubooru.func import posts +from szurubooru.model.bans import PostBan from szurubooru import db, errors, model, rest, search from szurubooru.func import ( @@ -29,18 +29,6 @@ def _serialize(ctx: rest.Context, ban: model.PostBan) -> rest.Response: ) -@rest.routes.post("/post-ban/(?P[^/]+)/?") -def ban_post(ctx: rest.Context, params: Dict[str, str]) -> rest.Response: - auth.verify_privilege(ctx.user, "posts:ban:create") - post = post_api._get_post(params) - versions.verify_version(post, ctx) - posts.ban(bans.create_ban(post)) - snapshots.delete(post, ctx.user) - posts.delete(post) - ctx.session.commit() - return {} - - @rest.routes.delete("/post-ban/(?P[^/]+)/?") def unban_post(ctx: rest.Context, params: Dict[str, str]) -> rest.Response: auth.verify_privilege(ctx.user, "posts:ban:delete") @@ -50,9 +38,21 @@ def unban_post(ctx: rest.Context, params: Dict[str, str]) -> rest.Response: return {} +@rest.routes.post("/post-ban/?") +def ban_post(ctx: rest.Context, params: Dict[str, str]) -> rest.Response: + auth.verify_privilege(ctx.user, "posts:ban:create") + # post = post_api._get_post(params) + post = posts.get_post_by_id(ctx.get_param_as_int("post_id")) + versions.verify_version(post, ctx) + posts.ban(bans.create_ban(post)) + snapshots.delete(post, ctx.user) + posts.delete(post) + ctx.session.commit() + return {} + @rest.routes.get("/post-ban/?") def get_bans(ctx: rest.Context, _params: Dict[str, str] = {}) -> rest.Response: auth.verify_privilege(ctx.user, "posts:ban:list") return _search_executor.execute_and_serialize( - ctx, lambda tag: _serialize(ctx, tag) + ctx, lambda ban: _serialize(ctx, ban) ) diff --git a/server/szurubooru/func/bans.py b/server/szurubooru/func/bans.py index 35946cc4..694dda62 100644 --- a/server/szurubooru/func/bans.py +++ b/server/szurubooru/func/bans.py @@ -15,7 +15,7 @@ class HashNotBannedError(errors.ValidationError): pass -class TagSerializer(serialization.BaseSerializer): +class BanSerializer(serialization.BaseSerializer): def __init__(self, ban: model.PostBan) -> None: self.ban = ban @@ -65,4 +65,4 @@ def serialize_ban( ) -> Optional[rest.Response]: if not ban: return None - return serialization.BaseSerializer(ban).serialize(options) + return BanSerializer(ban).serialize(options) diff --git a/server/szurubooru/func/posts.py b/server/szurubooru/func/posts.py index 7ec779f7..0348532b 100644 --- a/server/szurubooru/func/posts.py +++ b/server/szurubooru/func/posts.py @@ -4,7 +4,7 @@ from datetime import datetime from typing import Any, Callable, Dict, List, Optional, Tuple import sqlalchemy as sa -from server.szurubooru.func.bans import PostBannedError +from szurubooru.func.bans import PostBannedError from szurubooru import config, db, errors, model, rest from szurubooru.func import ( diff --git a/server/szurubooru/search/configs/ban_search_config.py b/server/szurubooru/search/configs/ban_search_config.py index 8977f4a9..ce8bba7d 100644 --- a/server/szurubooru/search/configs/ban_search_config.py +++ b/server/szurubooru/search/configs/ban_search_config.py @@ -14,17 +14,7 @@ from szurubooru.search.typing import SaColumn, SaQuery class BanSearchConfig(BaseSearchConfig): def create_filter_query(self, _disable_eager_loads: bool) -> SaQuery: - strategy = ( - sa.orm.lazyload if _disable_eager_loads else sa.orm.subqueryload - ) - return ( - db.session.query(model.PostBan) - .options( - sa.orm.defer(model.PostBan.checksum), - sa.orm.defer(model.PostBan.time), - sa.orm.defer(model.PostBan.ban_id) - ) - ) + return db.session.query(model.PostBan) def create_count_query(self, _disable_eager_loads: bool) -> SaQuery: return db.session.query(model.PostBan)