diff --git a/CHANGELOG.md b/CHANGELOG.md index 4884a3d..25a8067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,12 @@ - Switched to Quart framework. This implies everything is `async def` now. - **BREAKING**: `SERVER_NAME` env variable now contains the domain name. `DOMAIN_NAME` has been removed. - libsuou bumped to 0.6.0 -- Added several REST routes. Change needed due to pending frontend separation. +- Added several REST routes. Change needed due to pending [frontend separation](https://nekode.yusur.moe/yusur/vigil). - Deprecated the old web routes except for `/report` and `/admin` ## 0.4.0 -- Added dependency to [SUOU](https://github.com/sakuragasaki46/suou) library +- Added dependency to [SUOU](https://github.com/yusurko/suou) library - Users can now block each other + Blocking a user prevents them from seeing your comments, posts (standalone or in feed) and profile - Added user strikes: a strike logs the content of a removed message for future use diff --git a/freak/__init__.py b/freak/__init__.py index 70403af..0d829ac 100644 --- a/freak/__init__.py +++ b/freak/__init__.py @@ -26,7 +26,7 @@ from suou import twocolon_list, WantsContentType from .colors import color_themes, theme_classes -__version__ = '0.5.0-dev45' +__version__ = '0.5.0-dev47' APP_BASE_DIR = os.path.dirname(os.path.dirname(__file__)) @@ -40,11 +40,12 @@ class AppConfig(ConfigOptions): server_name = ConfigValue() force_server_name = ConfigValue(cast=yesno, default=True) private_assets = ConfigValue(cast=ssv_list) - # deprecated - jquery_url = ConfigValue(default='https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js') app_is_behind_proxy = ConfigValue(cast=int, default=0) impressum = ConfigValue(cast=twocolon_list, default='') create_guild_threshold = ConfigValue(cast=int, default=15, prefix='freak_') + # v-- deprecated --v + jquery_url = ConfigValue(default='https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js') + # ^----------------^ app_config = AppConfig() diff --git a/freak/rest/__init__.py b/freak/rest/__init__.py index c372fcd..39099d0 100644 --- a/freak/rest/__init__.py +++ b/freak/rest/__init__.py @@ -1,12 +1,16 @@ from __future__ import annotations +import datetime +import sys from typing import Iterable, TypeVar +import logging from quart import render_template, session from quart import abort, Blueprint, redirect, request, url_for -from pydantic import BaseModel +from pydantic import BaseModel, Field from quart_auth import current_user, login_required, login_user, logout_user from quart_schema import validate_request, validate_response +from quart_wtf.csrf import generate_csrf from sqlalchemy import delete, insert, select from suou import Snowflake, deprecated, makelist, not_implemented, want_isodate @@ -21,6 +25,7 @@ from freak.search import SearchQuery from ..models import Comment, Guild, Post, PostUpvote, User, db from .. import UserLoader, app, app_config, __version__ as freak_version, csrf +logger = logging.getLogger(__name__) _T = TypeVar('_T') bp = Blueprint('rest', __name__, url_prefix='/v1') @@ -64,8 +69,13 @@ async def oath(): ## pull csrf token from session csrf_tok = session['csrf_token'] except Exception as e: - print(e) - abort(503, "csrf_token is null") + try: + logger.warning('CSRF token regenerated!') + csrf_tok = session['csrf_token'] = generate_csrf() + except Exception as e2: + print(e, e2) + abort(503, "csrf_token is null") + return dict( ## XXX might break any time! csrf_token= csrf_tok @@ -266,6 +276,44 @@ async def guild_feed(gname: str): return dict(guilds={f'{Snowflake(gu.id):l}': gj}, feed=feed) + +## CREATE ## + +class CreateIn(BaseModel): + title: str + content: str + privacy: int = Field(default=0, ge=0, lt=4) + +@bp.post('/guild/@') +@login_required +@validate_request(CreateIn) +async def guild_post(data: CreateIn, gname: str): + async with db as session: + user = current_user.user + gu: Guild | None = (await session.execute(select(Guild).where(Guild.name == gname))).scalar() + + if gu is None: + return dict(error='Not found'), 404 + if await gu.has_exiled(current_user.user): + return dict(error=f'You are banned from +{gname}'), 403 + if not await gu.allows_posting(current_user.user): + return dict(error=f'You can\'t post on +{gname}'), 403 + + try: + new_post_id: int = (await session.execute(insert(Post).values( + author_id = user.id, + topic_id = gu.id, + privacy = data.privacy, + title = data.title, + text_content = data.text + ).returning(Post.id))).scalar() + + session.commit() + return dict(id=Snowflake(new_post_id).to_b32l()), 200 + except Exception: + sys.excepthook(*sys.exc_info()) + return {'error': 'Internal Server Error'}, 500 + ## LOGIN/OUT ## class LoginIn(BaseModel): diff --git a/freak/templates/reports/report_comment.html b/freak/templates/reports/report_comment.html index 8b8036e..aa00956 100644 --- a/freak/templates/reports/report_comment.html +++ b/freak/templates/reports/report_comment.html @@ -13,6 +13,9 @@ {{ opt.description }} {% endfor %} +
  • + I clicked "Report" by mistake +
  • {% endblock %} diff --git a/freak/templates/reports/report_post.html b/freak/templates/reports/report_post.html index 9e3167c..45c9954 100644 --- a/freak/templates/reports/report_post.html +++ b/freak/templates/reports/report_post.html @@ -13,6 +13,9 @@ {{ opt.description }} {% endfor %} +
  • + I clicked "Report" by mistake +
  • {% endblock %} diff --git a/freak/templates/terms.md b/freak/templates/terms.md index 467ca0c..9c9e3b8 100644 --- a/freak/templates/terms.md +++ b/freak/templates/terms.md @@ -1,19 +1,19 @@ # Terms of Service -This is a non-authoritative copy of the actual Terms, always updated at . +This is a non-authoritative copy of the actual Terms, always updated at . The following documents are incorporated into these Terms by reference (i.e. an extension to these Terms in force): * [Privacy Policy](/privacy) * [Community Guidelines](/rules) -* [User Generated Content Terms](https://yusur.moe/policies/ugc.html) on newdigitalspirit.com -* [Minors' Account Policy](https://yusur.moe/policies/u18.html) on newdigitalspirit.com +* [User Generated Content Terms](https://ndspir.it/ugc.html) on newdigitalspirit.com +* [Minors' Account Policy](https://ndspir.it/u18.html) on newdigitalspirit.com ## Scope and Definition -These terms of service ("Terms") are between **New Digital Spirit** and You, +These terms of service ("Terms") are between **{{ app_name }}** and You, regarding Your use of all sites and services belonging to New Digital Spirit ("New Digital Spirit Network" / "the Services"), listed in detail in [Privacy Policy](/policies/privacy.html). @@ -21,27 +21,27 @@ Other websites are not covered by these Terms. ## Age -The whole of New Digital Spirit Network is PG-13. You may not use the Services if you are younger than 13 years old. +The whole of {{ app_name }} is PG-13. You may not use the Services if you are younger than 13 years old. -Additionally, you may not directly contact New Digital Spirit if you are younger than 18 years old, for any reason besides +Additionally, you may not directly contact {{ app_name }} if you are younger than 18 years old, for any reason besides privacy-related requests. Any contact request knowingly from people younger than 18 will be ignored. United States resident under the age of 18 are **not allowed** in any way to access our network without logging in. -New Digital Spirit reserves the right to require ID verification in case of age doubt or potential security threat. +New Digital Spirit reserves the right to require ID verification in case of age doubt or suspected security threat. -Minors on New Digital Spirit Network are additionally bound to the [Minor Account Policy](/policies/u18.html), +Minors on New Digital Spirit Network are additionally bound to the [Minor Account Policy](https://ndspir.it/u18.html), incorporated here by reference. Systems and plurals are considered to be minors, no matter their body age. ## Intellectual property -Except otherwise noted, the entirety of the content on the New Digital Spirit Network -is intellectual property of Sakuragasaki46 and New Digital Spirit. All rights reserved. +Except otherwise noted, the entirety of the content on {{ app_name }} +is intellectual property of {{ app_name }}. All rights reserved. You may not copy, modify, redistribute, mirror the contents of or create alternative Service to -yusur.moe or any other of the Services, or portions thereof, without New Digital Spirit's +{{ server_name }} or any other of the Services, or portions thereof, without {{ app_name }}'s prior written permission. ## Privacy Rights @@ -53,7 +53,7 @@ identification or damages to Sakuragasaki46's private life. Disclosure will be legally regarded as a violation of privacy and a breach of non-disclosure agreement (NDA), and will be acted upon accordingly, regardless of the infringer's age or any other legal protection, included but not limited to -termination of the infringer,s accounts. +termination of the infringer's accounts. ## IP Loggers @@ -65,11 +65,11 @@ legitimate interest. Logged information contains user agent strings as well. ## User Generated Content Some of our Services allow user generated content. By using them, you agree to be bound -to the [User Generated Content Terms](/policies/ugc.html), incorporated here by reference. +to the [User Generated Content Terms](https://ndspir.it/ugc.html), incorporated here by reference. ## No Warranty -**Except as represented in this agreement, the New Digital Spirit Network +**Except as represented in this agreement, {{ app_name }} is provided ​“AS IS”. Other than as provided in this agreement, New Digital Spirit makes no other warranties, express or implied, and hereby disclaims all implied warranties, including any warranty of merchantability @@ -77,13 +77,13 @@ and warranty of fitness for a particular purpose.** ## Liability -Sakuragasaki46 or New Digital Spirit **shall not be accountable** for Your damages arising from Your use +{{ app_name }} **shall not be accountable** for Your damages arising from Your use of the New Digital Spirit Network. ## Indemnify You agree to [indemnify and hold harmless](https://www.upcounsel.com/difference-between-indemnify-and-hold-harmless) -Sakuragasaki46 and New Digital Spirit from any and all claims, damages, liabilities, costs and expenses, including reasonable and unreasonable +{{ app_name }} from any and all claims, damages, liabilities, costs and expenses, including reasonable and unreasonable counsel and attorney’s fees, arising out of any breach of this agreement. ## Severability @@ -95,7 +95,7 @@ according to the governing law, the remainder of these Terms shall remain in pla These terms of services are governed by, and shall be interpreted in accordance with, the laws of Italy. You consent to the sole jurisdiction of \[REDACTED], Italy -for all disputes between You and , and You consent to the sole +for all disputes between You and {{ app_name }}, and You consent to the sole application of Italian law and European Union law for all such disputes. ## Updates diff --git a/freak/website/admin.py b/freak/website/admin.py index accb25e..4497eff 100644 --- a/freak/website/admin.py +++ b/freak/website/admin.py @@ -103,7 +103,8 @@ async def accept_report(target, source: PostReport): await remove_content(target, source.reason_code) source.update_status = REPORT_UPDATE_COMPLETE - session.add(source) + # XXX disabled because of a session conflict + #session.add(source) @additem(REPORT_ACTIONS, '2') @@ -127,21 +128,21 @@ async def strike_report(target, source: PostReport): author.banned_reason = source.reason_code source.update_status = REPORT_UPDATE_COMPLETE - session.add(source) + #session.add(source) @additem(REPORT_ACTIONS, '0') async def reject_report(target, source: PostReport): async with db as session: source.update_status = REPORT_UPDATE_REJECTED - session.add(source) + #session.add(source) @additem(REPORT_ACTIONS, '3') async def withhold_report(target, source: PostReport): async with db as session: source.update_status = REPORT_UPDATE_ON_HOLD - session.add(source) + #session.add(source) @additem(REPORT_ACTIONS, '4') @@ -196,7 +197,7 @@ async def strikes(): @bp.route('/admin/users/') @admin_required async def users(): - user_list = await db.paginate(select(User).order_by(User.joined_at.desc())) + user_list = await db.paginate(select(User).order_by(User.joined_at.desc()), page=int(request.args.get('page', 1))) return await render_template('admin/admin_users.html', user_list=user_list, account_status_string=colorized_account_status_string) @@ -204,7 +205,7 @@ async def users(): @admin_required async def user_detail(id: int): async with db as session: - u = session.execute(select(User).where(User.id == id)).scalar() + u = (await session.execute(select(User).where(User.id == id))).scalar() if u is None: abort(404) if request.method == 'POST':