diff --git a/CHANGELOG.md b/CHANGELOG.md
index 40a4377..be61e29 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,8 +12,9 @@
- Moderators (and admins) have now access to mod tools
+ Allowed operations: change display name, description, restriction status, and exile (guild-local ban) members
+ Site administrators and guild owners can add moderators
-- Administrators can claim ownership of abandoned guilds
- Guilds can have restricted posting/commenting now. Unmoderated guilds always have.
+- Administrators can claim ownership of abandoned guilds
+- Admins can now suspend users from admin panel
- Implemented guild subscriptions (not as in $$$, yes as in the follow button)
- Minimum karma requirement for creating a guild is now configurable via env variable `FREAK_CREATE_GUILD_THRESHOLD` (previously hardcoded at 15)
- Users can now set their display name, biography and color theme in `/settings`
diff --git a/freak/models.py b/freak/models.py
index 6e68213..25f4485 100644
--- a/freak/models.py
+++ b/freak/models.py
@@ -54,7 +54,7 @@ post_report_reasons = [
REPORT_REASON_STRINGS = { **{x.num_code: x.description for x in post_report_reasons}, **{x.code: x.description for x in post_report_reasons} }
-REPORT_REASONS = {x.code: x.num_code for x in post_report_reasons}
+REPORT_REASONS: dict[str, int] = {x.code: x.num_code for x in post_report_reasons}
REPORT_TARGET_POST = 1
REPORT_TARGET_COMMENT = 2
@@ -175,7 +175,13 @@ class User(Base):
@property
def is_disabled(self):
- return (self.banned_at is not None and (self.banned_until is None or self.banned_until <= datetime.datetime.now())) or self.is_disabled_by_user
+ now = datetime.datetime.now()
+ return (
+ # suspended
+ (self.banned_at is not None and (self.banned_until is None or self.banned_until >= now)) or
+ # self-disabled
+ self.is_disabled_by_user
+ )
@property
def is_active(self):
diff --git a/freak/static/sass/layout.sass b/freak/static/sass/layout.sass
index 9325fb7..ae79fd1 100644
--- a/freak/static/sass/layout.sass
+++ b/freak/static/sass/layout.sass
@@ -310,6 +310,12 @@ button, [type="submit"], [type="reset"], [type="button"]
&[disabled]
opacity: .5
cursor: not-allowed
+ border: var(--border)
+ color: var(--border)
+
+ &.primary[disabled]
+ color: var(--background)
+ background-color: var(--border)
&:first-child
margin-inline-start: 0
diff --git a/freak/templates/403.html b/freak/templates/403.html
index f13fb50..ee4f511 100644
--- a/freak/templates/403.html
+++ b/freak/templates/403.html
@@ -2,7 +2,7 @@
{% from "macros/title.html" import title_tag with context %}
{% block title %}
-
{{ title_tag('X _ X') }}
+ {{ title_tag('X _ X') }}
{% endblock %}
{% block body %}
diff --git a/freak/templates/404.html b/freak/templates/404.html
index e3b427a..4a9f92b 100644
--- a/freak/templates/404.html
+++ b/freak/templates/404.html
@@ -2,7 +2,7 @@
{% from "macros/title.html" import title_tag with context %}
{% block title %}
- {{ title_tag('O _ O') }}
+ {{ title_tag('O _ O') }}
{% endblock %}
{% block body %}
diff --git a/freak/templates/405.html b/freak/templates/405.html
index 0151dcc..02c926b 100644
--- a/freak/templates/405.html
+++ b/freak/templates/405.html
@@ -2,7 +2,7 @@
{% from "macros/title.html" import title_tag with context %}
{% block title %}
- {{ title_tag('O _ O') }}
+ {{ title_tag('O _ O') }}
{% endblock %}
{% block body %}
diff --git a/freak/templates/admin/admin_user_detail.html b/freak/templates/admin/admin_user_detail.html
index 52b1cd3..36f8cb3 100644
--- a/freak/templates/admin/admin_user_detail.html
+++ b/freak/templates/admin/admin_user_detail.html
@@ -24,7 +24,23 @@
{% endif %}
+Quick Actions
Strikes
diff --git a/freak/website/admin.py b/freak/website/admin.py
index 1a75ed1..682f749 100644
--- a/freak/website/admin.py
+++ b/freak/website/admin.py
@@ -10,10 +10,12 @@ from markupsafe import Markup
from sqlalchemy import insert, select, update
from suou import additem, not_implemented
-from ..models import REPORT_REASON_STRINGS, REPORT_TARGET_COMMENT, REPORT_TARGET_POST, REPORT_UPDATE_COMPLETE, REPORT_UPDATE_ON_HOLD, REPORT_UPDATE_REJECTED, Comment, Post, PostReport, User, UserStrike, db
+from ..models import REPORT_REASON_STRINGS, REPORT_REASONS, REPORT_TARGET_COMMENT, REPORT_TARGET_POST, REPORT_UPDATE_COMPLETE, REPORT_UPDATE_ON_HOLD, REPORT_UPDATE_REJECTED, Comment, Post, PostReport, User, UserStrike, db
bp = Blueprint('admin', __name__)
+current_user: User
+
## TODO make admin interface
def admin_required(func: Callable):
@@ -191,7 +193,26 @@ def user_detail(id: int):
if u is None:
abort(404)
if request.method == 'POST':
- abort(501)
+ action = request.form['do']
+ if action == 'suspend':
+ u.banned_at = datetime.datetime.now()
+ u.banned_by_id = current_user.id
+ u.banned_reason = REPORT_REASONS.get(request.form.get('reason'), 0)
+ db.session.commit()
+ elif action == 'unsuspend':
+ u.banned_at = None
+ u.banned_by_id = None
+ u.banned_until = None
+ u.banned_reason = None
+ db.session.commit()
+ elif action == 'to_3d':
+ u.banned_at = datetime.datetime.now()
+ u.banned_until = datetime.datetime.now() + datetime.timedelta(days=3)
+ u.banned_by_id = current_user.id
+ u.banned_reason = REPORT_REASONS.get(request.form.get('reason'), 0)
+ db.session.commit()
+ else:
+ abort(400)
strikes = db.session.execute(select(UserStrike).where(UserStrike.user_id == id).order_by(UserStrike.id.desc())).scalars()
return render_template('admin/admin_user_detail.html', u=u,
report_reasons=REPORT_REASON_STRINGS, account_status_string=colorized_account_status_string, strikes=strikes)