Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔀 Added true_random sort that don't keep cache #2

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions client/html/user_edit.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@
</div>
</li>
<% } %>

<% if (ctx.canEditBlocklist) { %>
<li class='blocklist'>
<%= ctx.makeTextInput({text: 'Blocklist'}) %>
</li>
<% } %>
</ul>

<div class='messages'></div>
Expand Down
1 change: 1 addition & 0 deletions client/js/controllers/user_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class UserController {
canEditAvatar: api.hasPrivilege(
`users:edit:${infix}:avatar`
),
canEditBlocklist: api.hasPrivilege(`users:edit:${infix}:blocklist`),
canEditAnything: api.hasPrivilege(`users:edit:${infix}`),
canListTokens: api.hasPrivilege(
`userTokens:list:${infix}`
Expand Down
25 changes: 25 additions & 0 deletions client/js/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@
const api = require("../api.js");
const uri = require("../util/uri.js");
const events = require("../events.js");
const misc = require("../util/misc.js");

class User extends events.EventTarget {
constructor() {
const TagList = require("./tag_list.js");

super();
this._orig = {};

for (let obj of [this, this._orig]) {
obj._blocklist = new TagList();
}

this._updateFromResponse({});
}

Expand Down Expand Up @@ -71,6 +79,10 @@ class User extends events.EventTarget {
throw "Invalid operation";
}

get blocklist() {
return this._blocklist;
}

set name(value) {
this._name = value;
}
Expand All @@ -95,6 +107,10 @@ class User extends events.EventTarget {
this._password = value;
}

set blocklist(value) {
this._blocklist = value || "";
}

static fromResponse(response) {
const ret = new User();
ret._updateFromResponse(response);
Expand All @@ -121,6 +137,11 @@ class User extends events.EventTarget {
if (this._rank !== this._orig._rank) {
detail.rank = this._rank;
}
if (misc.arraysDiffer(this._blocklist, this._orig._blocklist)) {
detail.blocklist = this._blocklist.map(
(relation) => relation.names[0]
);
}
if (this._avatarStyle !== this._orig._avatarStyle) {
detail.avatarStyle = this._avatarStyle;
}
Expand Down Expand Up @@ -187,6 +208,10 @@ class User extends events.EventTarget {
_dislikedPostCount: response.dislikedPostCount,
};

for (let obj of [this, this._orig]) {
obj._blocklist.sync(response.blocklist);
}

Object.assign(this, map);
Object.assign(this._orig, map);

Expand Down
17 changes: 17 additions & 0 deletions client/js/views/user_edit_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const events = require("../events.js");
const api = require("../api.js");
const views = require("../util/views.js");
const FileDropperControl = require("../controls/file_dropper_control.js");
const TagInputControl = require("../controls/tag_input_control.js")
const misc = require("../util/misc.js");

const template = views.getTemplate("user-edit");

Expand Down Expand Up @@ -41,6 +43,13 @@ class UserEditView extends events.EventTarget {
});
}

if (this._blocklistFieldNode) {
new TagInputControl(
this._blocklistFieldNode,
this._user.blocklist
);
}

this._formNode.addEventListener("submit", (e) => this._evtSubmit(e));
}

Expand Down Expand Up @@ -83,6 +92,10 @@ class UserEditView extends events.EventTarget {
? this._rankInputNode.value
: undefined,

blocklist: this._blocklistFieldNode
? misc.splitByWhitespace(this._blocklistFieldNode.value)
: undefined,

avatarStyle: this._avatarStyleInputNode
? this._avatarStyleInputNode.value
: undefined,
Expand All @@ -101,6 +114,10 @@ class UserEditView extends events.EventTarget {
return this._hostNode.querySelector("form");
}

get _blocklistFieldNode() {
return this._formNode.querySelector(".blocklist input");
}

get _rankInputNode() {
return this._formNode.querySelector("[name=rank]");
}
Expand Down
8 changes: 8 additions & 0 deletions server/config.yaml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ webhooks:

default_rank: regular

# default blocklisted tags (space separated)
default_tag_blocklist: ''

# Apply blocklist for anonymous viewers too
default_tag_blocklist_for_anonymous: yes

privileges:
'users:create:self': anonymous # Registration permission
'users:create:any': administrator
Expand All @@ -76,11 +82,13 @@ privileges:
'users:edit:any:pass': moderator
'users:edit:any:email': moderator
'users:edit:any:avatar': moderator
'users:edit:any:blocklist': moderator
'users:edit:any:rank': moderator
'users:edit:self:name': regular
'users:edit:self:pass': regular
'users:edit:self:email': regular
'users:edit:self:avatar': regular
'users:edit:self:blocklist': regular
'users:edit:self:rank': moderator # one can't promote themselves or anyone to upper rank than their own.
'users:delete:any': administrator
'users:delete:self': regular
Expand Down
2 changes: 2 additions & 0 deletions server/szurubooru/api/info_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ def get_info(ctx: rest.Context, _params: Dict[str, str] = {}) -> rest.Response:
"tagNameRegex": config.config["tag_name_regex"],
"tagCategoryNameRegex": config.config["tag_category_name_regex"],
"defaultUserRank": config.config["default_rank"],
"defaultTagBlocklist": config.config["default_tag_blocklist"],
"defaultTagBlocklistForAnonymous": config.config["default_tag_blocklist_for_anonymous"],
"enableSafety": config.config["enable_safety"],
"contactEmail": config.config["contact_email"],
"canSendMails": bool(config.config["smtp"]["host"]),
Expand Down
2 changes: 1 addition & 1 deletion server/szurubooru/api/tag_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import Dict, List, Optional

from szurubooru import db, model, rest, search
from szurubooru.func import auth, serialization, snapshots, tags, versions
from szurubooru.func import auth, serialization, snapshots, tags, versions, users

_search_executor = search.Executor(search.configs.TagSearchConfig())

Expand Down
32 changes: 29 additions & 3 deletions server/szurubooru/api/user_api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Any, Dict
from typing import Any, Dict, List

from szurubooru import model, rest, search
from szurubooru.func import auth, serialization, users, versions
from szurubooru import db, model, rest, search
from szurubooru.func import auth, serialization, snapshots, users, versions, tags

_search_executor = search.Executor(search.configs.UserSearchConfig())

Expand All @@ -17,6 +17,18 @@ def _serialize(
)


def _create_tag_if_needed(tag_names: List[str], user: model.User) -> None:
# Taken from tag_api.py
if not tag_names:
return
_existing_tags, new_tags = tags.get_or_create_tags_by_names(tag_names)
if len(new_tags):
auth.verify_privilege(user, "tags:create")
db.session.flush()
for tag in new_tags:
snapshots.create(tag, user)


@rest.routes.get("/users/?")
def get_users(
ctx: rest.Context, _params: Dict[str, str] = {}
Expand Down Expand Up @@ -50,6 +62,10 @@ def create_user(
)
ctx.session.add(user)
ctx.session.commit()
to_add, _ = users.update_user_blocklist(user, None)
for e in to_add:
ctx.session.add(e)
ctx.session.commit()

return _serialize(ctx, user, force_show_email=True)

Expand Down Expand Up @@ -80,6 +96,16 @@ def update_user(ctx: rest.Context, params: Dict[str, str]) -> rest.Response:
if ctx.has_param("rank"):
auth.verify_privilege(ctx.user, "users:edit:%s:rank" % infix)
users.update_user_rank(user, ctx.get_param_as_string("rank"), ctx.user)
if ctx.has_param("blocklist"):
auth.verify_privilege(ctx.user, "users:edit:%s:blocklist" % infix)
blocklist = ctx.get_param_as_string_list("blocklist")
_create_tag_if_needed(blocklist, user) # Non-existing tags are created.
blocklist_tags = tags.get_tags_by_names(blocklist)
to_add, to_remove = users.update_user_blocklist(user, blocklist_tags)
for e in to_remove:
ctx.session.delete(e)
for e in to_add:
ctx.session.add(e)
if ctx.has_param("avatarStyle"):
auth.verify_privilege(ctx.user, "users:edit:%s:avatar" % infix)
users.update_user_avatar(
Expand Down
21 changes: 21 additions & 0 deletions server/szurubooru/func/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ def get_tag_by_name(name: str) -> model.Tag:


def get_tags_by_names(names: List[str]) -> List[model.Tag]:
"""
Returns a list of all tags which names include all the letters from the input list
"""
names = util.icase_unique(names)
if len(names) == 0:
return []
Expand All @@ -175,6 +178,24 @@ def get_tags_by_names(names: List[str]) -> List[model.Tag]:
)


def get_tags_by_exact_names(names: List[str]) -> List[model.Tag]:
"""
Returns a list of tags matching the names from the input list
"""
entries = []
if len(names) == 0:
return []
names = [name.lower() for name in names]
entries = (
db.session.query(model.Tag)
.join(model.TagName)
.filter(
sa.func.lower(model.TagName.name).in_(names)
)
.all())
return entries


def get_or_create_tags_by_names(
names: List[str],
) -> Tuple[List[model.Tag], List[model.Tag]]:
Expand Down
67 changes: 66 additions & 1 deletion server/szurubooru/func/users.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import copy
import re
from datetime import datetime
from typing import Any, Callable, Dict, List, Optional, Union

import sqlalchemy as sa

from szurubooru import config, db, errors, model, rest
from szurubooru.func import auth, files, images, serialization, util
from szurubooru.func import auth, files, images, serialization, util, tags


class UserNotFoundError(errors.NotFoundError):
Expand Down Expand Up @@ -107,6 +108,7 @@ def _serializers(self) -> Dict[str, Callable[[], Any]]:
"lastLoginTime": self.serialize_last_login_time,
"version": self.serialize_version,
"rank": self.serialize_rank,
"blocklist": self.serialize_blocklist,
"avatarStyle": self.serialize_avatar_style,
"avatarUrl": self.serialize_avatar_url,
"commentCount": self.serialize_comment_count,
Expand Down Expand Up @@ -138,6 +140,9 @@ def serialize_avatar_style(self) -> Any:
def serialize_avatar_url(self) -> Any:
return get_avatar_url(self.user)

def serialize_blocklist(self) -> Any:
return [tags.serialize_tag(tag) for tag in get_blocklist_tag_from_user(self.user)]

def serialize_comment_count(self) -> Any:
return self.user.comment_count

Expand Down Expand Up @@ -294,6 +299,66 @@ def update_user_rank(
user.rank = rank


def get_blocklist_from_user(user: model.User) -> List[model.UserTagBlocklist]:
"""
Return the UserTagBlocklist objects related to given user
"""
rez = (db.session.query(model.UserTagBlocklist)
.filter(
model.UserTagBlocklist.user_id == user.user_id
)
.all())
return rez


def get_blocklist_tag_from_user(user: model.User) -> List[model.UserTagBlocklist]:
"""
Return the Tags blocklisted by given user
"""
rez = (db.session.query(model.UserTagBlocklist.tag_id)
.filter(
model.UserTagBlocklist.user_id == user.user_id
))
rez2 = (db.session.query(model.Tag)
.filter(
model.Tag.tag_id.in_(rez)
).all())
return rez2


def update_user_blocklist(user: model.User, new_blocklist_tags: Optional[List[model.Tag]]) -> List[List[model.UserTagBlocklist]]:
"""
Modify blocklist for given user.
If new_blocklist_tags is None, set the blocklist to configured default tag blocklist.
"""
assert user
to_add: List[model.UserTagBlocklist] = []
to_remove: List[model.UserTagBlocklist] = []

if new_blocklist_tags is None: # We're creating the user, use default config blocklist
if 'default_tag_blocklist' in config.config.keys():
for e in tags.get_tags_by_exact_names(config.config['default_tag_blocklist'].split(' ')):
to_add.append(model.UserTagBlocklist(user_id=user.user_id, tag_id=e.tag_id))
else:
new_blocklist_ids: List[int] = [e.tag_id for e in new_blocklist_tags]
previous_blocklist_tags: List[model.Tag] = get_blocklist_from_user(user)
previous_blocklist_ids: List[int] = [e.tag_id for e in previous_blocklist_tags]
original_previous_blocklist_ids = copy.copy(previous_blocklist_ids)

## Remove tags no longer in the new list
for i in range(len(original_previous_blocklist_ids)):
old_tag_id = original_previous_blocklist_ids[i]
if old_tag_id not in new_blocklist_ids:
to_remove.append(previous_blocklist_tags[i])
previous_blocklist_ids.remove(old_tag_id)

## Add tags not yet in the original list
for new_tag_id in new_blocklist_ids:
if new_tag_id not in previous_blocklist_ids:
to_add.append(model.UserTagBlocklist(user_id=user.user_id, tag_id=new_tag_id))
return to_add, to_remove


def update_user_avatar(
user: model.User, avatar_style: str, avatar_content: Optional[bytes] = None
) -> None:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'''
Add blocklist related fields

add_blocklist

Revision ID: 9ba5e3a6ee7c
Created at: 2023-05-20 22:28:10.824954
'''

import sqlalchemy as sa
from alembic import op

revision = '9ba5e3a6ee7c'
down_revision = 'adcd63ff76a2'
branch_labels = None
depends_on = None


def upgrade():
op.create_table(
"user_tag_blocklist",
sa.Column("user_id", sa.Integer(), nullable=False),
sa.Column("tag_id", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(["user_id"], ["user.id"]),
sa.ForeignKeyConstraint(["tag_id"], ["tag.id"]),
sa.PrimaryKeyConstraint("user_id", "tag_id"),
)

def downgrade():
op.drop_table('user_tag_blocklist')
Loading