Add more ban API endpoints
This commit is contained in:
parent
a7f6547f18
commit
68c2f8f676
11 changed files with 222 additions and 36 deletions
|
@ -116,7 +116,9 @@ privileges:
|
||||||
'posts:bulk-edit:tags': power
|
'posts:bulk-edit:tags': power
|
||||||
'posts:bulk-edit:safety': power
|
'posts:bulk-edit:safety': power
|
||||||
'posts:bulk-edit:delete': power
|
'posts:bulk-edit:delete': power
|
||||||
'posts:ban': moderator
|
'posts:ban:create': moderator
|
||||||
|
'posts:ban:delete': moderator
|
||||||
|
'posts:ban:list': moderator
|
||||||
|
|
||||||
'tags:create': regular
|
'tags:create': regular
|
||||||
'tags:edit:names': power
|
'tags:edit:names': power
|
||||||
|
|
|
@ -10,3 +10,4 @@ import szurubooru.api.tag_category_api
|
||||||
import szurubooru.api.upload_api
|
import szurubooru.api.upload_api
|
||||||
import szurubooru.api.user_api
|
import szurubooru.api.user_api
|
||||||
import szurubooru.api.user_token_api
|
import szurubooru.api.user_token_api
|
||||||
|
import szurubooru.api.ban_api
|
58
server/szurubooru/api/ban_api.py
Normal file
58
server/szurubooru/api/ban_api.py
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
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 import db, errors, model, rest, search
|
||||||
|
from szurubooru.func import (
|
||||||
|
auth,
|
||||||
|
bans,
|
||||||
|
serialization,
|
||||||
|
snapshots,
|
||||||
|
versions,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_ban_by_hash(hash: str) -> Optional[PostBan]:
|
||||||
|
try:
|
||||||
|
return bans.get_bans_by_hash(hash)
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
_search_executor = search.Executor(search.configs.BanSearchConfig())
|
||||||
|
|
||||||
|
|
||||||
|
def _serialize(ctx: rest.Context, ban: model.PostBan) -> rest.Response:
|
||||||
|
return bans.serialize_ban(
|
||||||
|
ban, options=serialization.get_serialization_options(ctx)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@rest.routes.post("/post-ban/(?P<post_id>[^/]+)/?")
|
||||||
|
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<image_hash>[^/]+)/?")
|
||||||
|
def unban_post(ctx: rest.Context, params: Dict[str, str]) -> rest.Response:
|
||||||
|
auth.verify_privilege(ctx.user, "posts:ban:delete")
|
||||||
|
ban = _get_ban_by_hash(params["image_hash"])
|
||||||
|
bans.delete(ban)
|
||||||
|
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)
|
||||||
|
)
|
|
@ -183,18 +183,6 @@ def delete_post(ctx: rest.Context, params: Dict[str, str]) -> rest.Response:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
@rest.routes.post("/post-ban/(?P<post_id>[^/]+)/?")
|
|
||||||
def ban_post(ctx: rest.Context, params: Dict[str, str]) -> rest.Response:
|
|
||||||
auth.verify_privilege(ctx.user, "posts:ban")
|
|
||||||
post = _get_post(params)
|
|
||||||
versions.verify_version(post, ctx)
|
|
||||||
posts.ban(posts.create_ban(post))
|
|
||||||
snapshots.delete(post, ctx.user)
|
|
||||||
posts.delete(post)
|
|
||||||
ctx.session.commit()
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
@rest.routes.post("/post-merge/?")
|
@rest.routes.post("/post-merge/?")
|
||||||
def merge_posts(
|
def merge_posts(
|
||||||
ctx: rest.Context, _params: Dict[str, str] = {}
|
ctx: rest.Context, _params: Dict[str, str] = {}
|
||||||
|
|
68
server/szurubooru/func/bans.py
Normal file
68
server/szurubooru/func/bans.py
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
from szurubooru import db, errors, model, rest
|
||||||
|
from szurubooru.func import (
|
||||||
|
serialization,
|
||||||
|
)
|
||||||
|
|
||||||
|
class PostBannedError(errors.ValidationError):
|
||||||
|
def __init__(self, message: str = "This file was banned", extra_fields: Dict[str, str] = None) -> None:
|
||||||
|
super().__init__(message, extra_fields)
|
||||||
|
|
||||||
|
|
||||||
|
class HashNotBannedError(errors.ValidationError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TagSerializer(serialization.BaseSerializer):
|
||||||
|
def __init__(self, ban: model.PostBan) -> None:
|
||||||
|
self.ban = ban
|
||||||
|
|
||||||
|
def _serializers(self) -> Dict[str, Callable[[], Any]]:
|
||||||
|
return {
|
||||||
|
"checksum": self.serialize_checksum,
|
||||||
|
"time": self.serialize_time
|
||||||
|
}
|
||||||
|
|
||||||
|
def serialize_checksum(self) -> Any:
|
||||||
|
return self.ban.checksum
|
||||||
|
|
||||||
|
def serialize_time(self) -> Any:
|
||||||
|
return self.ban.time
|
||||||
|
|
||||||
|
|
||||||
|
def create_ban(post: model.Post) -> model.PostBan:
|
||||||
|
ban = model.PostBan()
|
||||||
|
ban.checksum = post.checksum
|
||||||
|
ban.time = datetime.utcnow()
|
||||||
|
|
||||||
|
db.session.add(ban)
|
||||||
|
return ban
|
||||||
|
|
||||||
|
|
||||||
|
def try_get_ban_by_checksum(checksum: str) -> Optional[model.PostBan]:
|
||||||
|
return (
|
||||||
|
db.session.query(model.PostBan)
|
||||||
|
.filter(model.PostBan.checksum == checksum)
|
||||||
|
.one_or_none()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_bans_by_hash(hash: str) -> model.PostBan:
|
||||||
|
ban = try_get_ban_by_checksum(hash)
|
||||||
|
if ban is None:
|
||||||
|
raise HashNotBannedError("Hash %s is not banned" % hash)
|
||||||
|
return ban
|
||||||
|
|
||||||
|
|
||||||
|
def delete(ban: model.PostBan) -> None:
|
||||||
|
db.session.delete(ban)
|
||||||
|
|
||||||
|
|
||||||
|
def serialize_ban(
|
||||||
|
ban: model.PostBan, options: List[str] = []
|
||||||
|
) -> Optional[rest.Response]:
|
||||||
|
if not ban:
|
||||||
|
return None
|
||||||
|
return serialization.BaseSerializer(ban).serialize(options)
|
|
@ -4,6 +4,7 @@ from datetime import datetime
|
||||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from server.szurubooru.func.bans import PostBannedError
|
||||||
|
|
||||||
from szurubooru import config, db, errors, model, rest
|
from szurubooru import config, db, errors, model, rest
|
||||||
from szurubooru.func import (
|
from szurubooru.func import (
|
||||||
|
@ -50,11 +51,6 @@ class PostAlreadyUploadedError(errors.ValidationError):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PostBannedError(errors.ValidationError):
|
|
||||||
def __init__(self, message: str = "This file was banned", extra_fields: Dict[str, str] = None) -> None:
|
|
||||||
super().__init__(message, extra_fields)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidPostIdError(errors.ValidationError):
|
class InvalidPostIdError(errors.ValidationError):
|
||||||
pass
|
pass
|
||||||
|
@ -431,15 +427,6 @@ def create_post(
|
||||||
return post, new_tags
|
return post, new_tags
|
||||||
|
|
||||||
|
|
||||||
def create_ban(post: model.Post) -> model.PostBan:
|
|
||||||
ban = model.PostBan()
|
|
||||||
ban.checksum = post.checksum
|
|
||||||
ban.time = datetime.utcnow()
|
|
||||||
|
|
||||||
db.session.add(ban)
|
|
||||||
return ban
|
|
||||||
|
|
||||||
|
|
||||||
def update_post_safety(post: model.Post, safety: str) -> None:
|
def update_post_safety(post: model.Post, safety: str) -> None:
|
||||||
assert post
|
assert post
|
||||||
safety = util.flip(SAFETY_MAP).get(safety, None)
|
safety = util.flip(SAFETY_MAP).get(safety, None)
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import szurubooru.model.util
|
import szurubooru.model.util
|
||||||
from szurubooru.model.base import Base
|
from szurubooru.model.base import Base
|
||||||
|
from szurubooru.model.bans import PostBan
|
||||||
from szurubooru.model.comment import Comment, CommentScore
|
from szurubooru.model.comment import Comment, CommentScore
|
||||||
from szurubooru.model.pool import Pool, PoolName, PoolPost
|
from szurubooru.model.pool import Pool, PoolName, PoolPost
|
||||||
from szurubooru.model.pool_category import PoolCategory
|
from szurubooru.model.pool_category import PoolCategory
|
||||||
from szurubooru.model.post import (
|
from szurubooru.model.post import (
|
||||||
Post,
|
Post,
|
||||||
PostBan,
|
|
||||||
PostFavorite,
|
PostFavorite,
|
||||||
PostFeature,
|
PostFeature,
|
||||||
PostNote,
|
PostNote,
|
||||||
|
|
12
server/szurubooru/model/bans.py
Normal file
12
server/szurubooru/model/bans.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
from szurubooru.model.base import Base
|
||||||
|
|
||||||
|
class PostBan(Base):
|
||||||
|
__tablename__ = "post_ban"
|
||||||
|
|
||||||
|
ban_id = sa.Column("id", sa.Integer, primary_key=True)
|
||||||
|
checksum = sa.Column("checksum", sa.Unicode(64), nullable=False)
|
||||||
|
time = sa.Column("time", sa.DateTime, nullable=False)
|
|
@ -94,14 +94,6 @@ class PostFavorite(Base):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PostBan(Base):
|
|
||||||
__tablename__ = "post_ban"
|
|
||||||
|
|
||||||
ban_id = sa.Column("id", sa.Integer, primary_key=True)
|
|
||||||
checksum = sa.Column("checksum", sa.Unicode(64), nullable=False)
|
|
||||||
time = sa.Column("time", sa.DateTime, nullable=False)
|
|
||||||
|
|
||||||
|
|
||||||
class PostNote(Base):
|
class PostNote(Base):
|
||||||
__tablename__ = "post_note"
|
__tablename__ = "post_note"
|
||||||
|
|
||||||
|
|
|
@ -4,3 +4,4 @@ from .post_search_config import PostSearchConfig
|
||||||
from .snapshot_search_config import SnapshotSearchConfig
|
from .snapshot_search_config import SnapshotSearchConfig
|
||||||
from .tag_search_config import TagSearchConfig
|
from .tag_search_config import TagSearchConfig
|
||||||
from .user_search_config import UserSearchConfig
|
from .user_search_config import UserSearchConfig
|
||||||
|
from .ban_search_config import BanSearchConfig
|
77
server/szurubooru/search/configs/ban_search_config.py
Normal file
77
server/szurubooru/search/configs/ban_search_config.py
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
from typing import Dict, Tuple
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
from szurubooru import db, model
|
||||||
|
from szurubooru.func import util
|
||||||
|
from szurubooru.search.configs import util as search_util
|
||||||
|
from szurubooru.search.configs.base_search_config import (
|
||||||
|
BaseSearchConfig,
|
||||||
|
Filter,
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_count_query(self, _disable_eager_loads: bool) -> SaQuery:
|
||||||
|
return db.session.query(model.PostBan)
|
||||||
|
|
||||||
|
def create_around_query(self) -> SaQuery:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def finalize_query(self, query: SaQuery) -> SaQuery:
|
||||||
|
return query.order_by(model.PostBan.time.asc())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def anonymous_filter(self) -> Filter:
|
||||||
|
return search_util.create_subquery_filter(
|
||||||
|
model.PostBan.checksum,
|
||||||
|
model.PostBan.time,
|
||||||
|
search_util.create_str_filter,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def named_filters(self) -> Dict[str, Filter]:
|
||||||
|
return util.unalias_dict(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
["time"],
|
||||||
|
search_util.create_date_filter(
|
||||||
|
model.PostBan.time,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
["checksum"],
|
||||||
|
search_util.create_subquery_filter(
|
||||||
|
model.PostBan.checksum,
|
||||||
|
search_util.create_str_filter,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sort_columns(self) -> Dict[str, Tuple[SaColumn, str]]:
|
||||||
|
return util.unalias_dict(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
["random"],
|
||||||
|
(sa.sql.expression.func.random(), self.SORT_NONE),
|
||||||
|
),
|
||||||
|
(["checksum"], (model.PostBan.checksum, self.SORT_ASC)),
|
||||||
|
(["time"], (model.PostBan.time, self.SORT_ASC))
|
||||||
|
]
|
||||||
|
)
|
Loading…
Reference in a new issue