add /v1/settings/appearance, bump suou requirement

This commit is contained in:
Yusur 2025-11-01 09:54:55 +01:00
parent 41be26d484
commit d1f33afe09
4 changed files with 51 additions and 7 deletions

View file

@ -26,7 +26,7 @@ from suou import twocolon_list, WantsContentType
from .colors import color_themes, theme_classes from .colors import color_themes, theme_classes
__version__ = '0.5.0-dev42' __version__ = '0.5.0-dev43'
APP_BASE_DIR = os.path.dirname(os.path.dirname(__file__)) APP_BASE_DIR = os.path.dirname(os.path.dirname(__file__))

View file

@ -5,6 +5,7 @@ import enum
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from suou.sqlalchemy.asyncio import AsyncSession
from .models import User, db from .models import User, db
from quart_auth import AuthUser, Action as _Action from quart_auth import AuthUser, Action as _Action
@ -42,7 +43,7 @@ class UserLoader(AuthUser):
def __init__(self, auth_id: str | None, action: _Action= _Action.PASS): def __init__(self, auth_id: str | None, action: _Action= _Action.PASS):
self._auth_id = auth_id self._auth_id = auth_id
self._auth_obj = None self._auth_obj = None
self._auth_sess = None self._auth_sess: AsyncSession | None = None
self.action = action self.action = action
@property @property
@ -69,6 +70,10 @@ class UserLoader(AuthUser):
def __bool__(self): def __bool__(self):
return self._auth_obj is not None return self._auth_obj is not None
@property
def session(self):
return self._auth_sess
async def _unload(self): async def _unload(self):
# user is not expected to mutate # user is not expected to mutate
if self._auth_sess: if self._auth_sess:

View file

@ -1,15 +1,16 @@
from __future__ import annotations from __future__ import annotations
from typing import Iterable from typing import Iterable, TypeVar
from quart import session from quart import session
from quart import abort, Blueprint, redirect, request, url_for from quart import abort, Blueprint, redirect, request, url_for
from pydantic import BaseModel from pydantic import BaseModel
from quart_auth import AuthUser, current_user, login_required, login_user, logout_user from quart_auth import current_user, login_required, login_user, logout_user
from quart_schema import QuartSchema, validate_request, validate_response from quart_schema import validate_request, validate_response
from sqlalchemy import delete, insert, select from sqlalchemy import delete, insert, select
from suou import Snowflake, deprecated, makelist, not_implemented, want_isodate from suou import Snowflake, deprecated, makelist, not_implemented, want_isodate
from suou.classtools import MISSING, MissingType
from werkzeug.security import check_password_hash from werkzeug.security import check_password_hash
from suou.quart import add_rest from suou.quart import add_rest
@ -20,6 +21,8 @@ from freak.search import SearchQuery
from ..models import Comment, Guild, Post, PostUpvote, User, db from ..models import Comment, Guild, Post, PostUpvote, User, db
from .. import UserLoader, app, app_config, __version__ as freak_version, csrf from .. import UserLoader, app, app_config, __version__ as freak_version, csrf
_T = TypeVar('_T')
bp = Blueprint('rest', __name__, url_prefix='/v1') bp = Blueprint('rest', __name__, url_prefix='/v1')
rest = add_rest(app, '/v1', '/ajax') rest = add_rest(app, '/v1', '/ajax')
@ -332,7 +335,7 @@ async def search_top(data: QueryIn):
async with db as session: async with db as session:
sq = SearchQuery(data.query) sq = SearchQuery(data.query)
result: Iterable[Post] = (await session.execute(sq.select(Post, [Post.title]).limit(20))).scalars() result = (await session.execute(sq.select(Post, [Post.title]).limit(20))).scalars()
return dict(has = [p.feed_info() for p in result]) return dict(has = [p.feed_info() for p in result])
@ -353,3 +356,39 @@ async def suggest_guild(data: QueryIn):
return dict(has = [g.simple_info() for g in result if await g.allows_posting(current_user.user)]) return dict(has = [g.simple_info() for g in result if await g.allows_posting(current_user.user)])
## SETTINGS
@bp.get("/settings/appearance")
@login_required
async def get_settings_appearance():
return dict(
color_theme = current_user.user.color_theme
)
class SettingsAppearanceIn(BaseModel):
color_theme : int | None = None
color_scheme : int | None = None
def _missing_or(obj: _T | MissingType, obj2: _T) -> _T:
if obj is None:
return obj2
return obj
@bp.patch("/settings/appearance")
@login_required
@validate_request(SettingsAppearanceIn)
async def patch_settings_appearance(data: SettingsIn):
u = current_user.user
if u is None:
abort(401)
u.color_theme = (
_missing_or(data.color_theme, u.color_theme % (1 << 8)) % 256 +
_missing_or(data.color_scheme, u.color_theme >> 8) << 8
)
current_user.session.add(u)
await current_user.session.commit()
return '', 204

View file

@ -19,7 +19,7 @@ dependencies = [
"libsass", "libsass",
"setuptools>=78.1.0", "setuptools>=78.1.0",
"Hypercorn", "Hypercorn",
"suou>=0.6.1" "suou[sqlalchemy]>=0.7.5"
] ]
requires-python = ">=3.10" requires-python = ">=3.10"
classifiers = [ classifiers = [