diff --git a/amanuensis/backend/lexicon.py b/amanuensis/backend/lexicon.py index ff83b34..d2dab71 100644 --- a/amanuensis/backend/lexicon.py +++ b/amanuensis/backend/lexicon.py @@ -5,7 +5,8 @@ Lexicon query interface import re from typing import Sequence, Optional -from sqlalchemy import select, func +from sqlalchemy import select, func, update +from werkzeug.security import generate_password_hash, check_password_hash from amanuensis.db import DbContext, Lexicon, Membership from amanuensis.errors import ArgumentError, BackendArgumentTypeError @@ -72,6 +73,21 @@ def get_public(db: DbContext) -> Sequence[Lexicon]: return db(select(Lexicon).where(Lexicon.public == True)).scalars() +def password_check(db: DbContext, lexicon_id: int, password: str) -> bool: + """Check if a password is correct.""" + password_hash: str = db( + select(Lexicon.join_password).where(Lexicon.id == lexicon_id) + ).scalar_one() + return check_password_hash(password_hash, password) + + +def password_set(db: DbContext, lexicon_id: int, new_password: Optional[str]) -> None: + """Set or clear a lexicon's password.""" + password_hash = generate_password_hash(new_password) if new_password else None + db(update(Lexicon).where(Lexicon.id == lexicon_id).values(password=password_hash)) + db.session.commit() + + def try_from_name(db: DbContext, name: str) -> Optional[Lexicon]: """Get a lexicon by its name, or None if no such lexicon was found.""" return db(select(Lexicon).where(Lexicon.name == name)).scalar_one_or_none() diff --git a/amanuensis/backend/membership.py b/amanuensis/backend/membership.py index bd111c9..dff50e2 100644 --- a/amanuensis/backend/membership.py +++ b/amanuensis/backend/membership.py @@ -68,4 +68,8 @@ def create( def try_from_ids(db: DbContext, user_id: int, lexicon_id: int) -> Membership: """Get a membership by the user and lexicon ids, or None if no such membership was found.""" - return db(select(Membership).where(Membership.user_id == user_id).where(Membership.lexicon_id == lexicon_id)).scalar_one_or_none() + return db( + select(Membership) + .where(Membership.user_id == user_id) + .where(Membership.lexicon_id == lexicon_id) + ).scalar_one_or_none() diff --git a/amanuensis/backend/user.py b/amanuensis/backend/user.py index e07e315..3452f56 100644 --- a/amanuensis/backend/user.py +++ b/amanuensis/backend/user.py @@ -76,13 +76,6 @@ def get_all(db: DbContext) -> Sequence[User]: return db(select(User)).scalars() -def password_set(db: DbContext, username: str, new_password: str) -> None: - """Set a user's password.""" - password_hash = generate_password_hash(new_password) - db(update(User).where(User.username == username).values(password=password_hash)) - db.session.commit() - - def password_check(db: DbContext, username: str, password: str) -> bool: """Check if a password is correct.""" user_password_hash: str = db( @@ -91,6 +84,13 @@ def password_check(db: DbContext, username: str, password: str) -> bool: return check_password_hash(user_password_hash, password) +def password_set(db: DbContext, username: str, new_password: str) -> None: + """Set a user's password.""" + password_hash = generate_password_hash(new_password) + db(update(User).where(User.username == username).values(password=password_hash)) + db.session.commit() + + def try_from_id(db: DbContext, user_id: int) -> Optional[User]: """Get a user by the user's id, or None is no such user was found.""" return db(select(User).where(User.id == user_id)).scalar_one_or_none() diff --git a/amanuensis/lexicon/setup.py b/amanuensis/lexicon/setup.py index 22c738b..e9d59c5 100644 --- a/amanuensis/lexicon/setup.py +++ b/amanuensis/lexicon/setup.py @@ -11,58 +11,6 @@ from amanuensis.models import LexiconModel, UserModel from amanuensis.resources import get_stream -def player_can_join_lexicon( - player: UserModel, - lexicon: LexiconModel, - password: str = None) -> bool: - """ - Checks whether the given player can join a lexicon - """ - # Trivial failures - if lexicon is None: - return False - if player is None: - return False - # Can't join if already in the game - if player.uid in lexicon.cfg.join.joined: - return False - # Can't join if the game is closed - if not lexicon.cfg.join.open: - return False - # Can't join if there's no room left - if len(lexicon.cfg.join.joined) >= lexicon.cfg.join.max_players: - return False - # Can't join if the password doesn't check out - if (lexicon.cfg.join.password is not None - and lexicon.cfg.join.password != password): - return False - return True - - -def add_player_to_lexicon( - player: UserModel, - lexicon: LexiconModel) -> None: - """ - Unconditionally adds a player to a lexicon - """ - # Verify arguments - if lexicon is None: - raise ArgumentError(f'Invalid lexicon: {lexicon}') - if player is None: - raise ArgumentError(f'Invalid player: {player}') - - # Idempotently add player - added = False - with lexicon.ctx.edit_config() as cfg: - if player.uid not in cfg.join.joined: - cfg.join.joined.append(player.uid) - added = True - - # Log to the lexicon's log - if added: - lexicon.log('Player "{0.cfg.username}" joined ({0.uid})'.format(player)) - - def player_can_create_character( player: UserModel, lexicon: LexiconModel, diff --git a/amanuensis/server/__init__.py b/amanuensis/server/__init__.py index 5ffd770..5e845f8 100644 --- a/amanuensis/server/__init__.py +++ b/amanuensis/server/__init__.py @@ -2,7 +2,7 @@ from datetime import datetime, timezone import json import os -from flask import Flask, g, url_for +from flask import Flask, g, url_for, redirect from amanuensis.backend import lexiq, userq, memq from amanuensis.config import AmanuensisConfig, CommandLineConfig @@ -10,6 +10,7 @@ from amanuensis.db import DbContext from amanuensis.parser import filesafe_title import amanuensis.server.auth as auth import amanuensis.server.home as home +import amanuensis.server.lexicon as lexicon def date_format(dt: datetime, formatstr="%Y-%m-%d %H:%M:%S%z") -> str: @@ -80,11 +81,13 @@ def get_app( # Register blueprints app.register_blueprint(auth.bp) app.register_blueprint(home.bp) + app.register_blueprint(lexicon.bp) - def test(): - return "Hello, world!" + # Add a root redirect + def root(): + return redirect(url_for("home.home")) - app.route("/")(test) + app.route("/")(root) return app diff --git a/amanuensis/server/helpers.py b/amanuensis/server/helpers.py index 0af052f..c68c190 100644 --- a/amanuensis/server/helpers.py +++ b/amanuensis/server/helpers.py @@ -15,8 +15,9 @@ def lexicon_param(route): """ @wraps(route) def with_lexicon(*args, **kwargs): + db: DbContext = g.db name: str = kwargs.get('name') - lexicon: Optional[Lexicon] = lexiq.try_from_name(name) + lexicon: Optional[Lexicon] = lexiq.try_from_name(db, name) if lexicon is None: flash(f"Couldn't find a lexicon with the name \"{name}\"") return redirect(url_for("home.home")) diff --git a/amanuensis/server/lexicon.jinja b/amanuensis/server/lexicon.jinja index 87c1a15..4940500 100644 --- a/amanuensis/server/lexicon.jinja +++ b/amanuensis/server/lexicon.jinja @@ -1,44 +1,44 @@ {% extends "page_2col.jinja" %} -{% set lexicon_title = g.lexicon.title %} +{% set lexicon_title = g.lexicon.full_title %} {% block header %}
{{ g.lexicon.cfg.prompt }}
+{{ g.lexicon.prompt }}
{% endblock %} {% block sb_logo %}{% endblock %} {% block sb_home %}Home {% endblock %} {% block sb_contents %}Contents{% endblock %} + {% if current_page == "contents" %}class="current-page" + {% else %}href="{{ url_for('lexicon.contents', name=g.lexicon.name) }}" + {% endif %}>Contents{% endblock %} {% block sb_rules %}Rules{% endblock %} + {% if current_page == "rules" %}class="current-page" + {% else %}href="{{ url_for('lexicon.rules', name=g.lexicon.name) }}" + {% endif %}>Rules{% endblock %} {% block sb_session %}Session{% endblock %} + {% if current_page == "session" %}class="current-page" + {% else %}href="#{#{ url_for('session.session', name=g.lexicon.name) }#}" + {% endif %}>Session{% endblock %} {% block sb_stats %}Statistics{% endblock %} + {% if current_page == "statistics" %}class="current-page" + {% else %}href="{{ url_for('lexicon.stats', name=g.lexicon.name) }}" + {% endif %}>Statistics{% endblock %} -{% if current_user.uid in g.lexicon.cfg.join.joined %} - {# self.sb_logo(), #} +{% if current_user.is_authenticated and memq.try_from_ids(g.db, current_user.id, g.lexicon.id) %} + {# self.sb_logo(), #} {% set template_sidebar_rows = [ - self.sb_home(), - self.sb_contents(), - self.sb_rules(), - self.sb_session(), - self.sb_stats()] %} + self.sb_home(), + self.sb_contents(), + self.sb_rules(), + self.sb_session(), + self.sb_stats()] %} {% else %} - {# self.sb_logo(), #} + {# self.sb_logo(), #} {% set template_sidebar_rows = [ - self.sb_home(), - self.sb_contents(), - self.sb_rules(), - self.sb_stats()] %} + self.sb_home(), + self.sb_contents(), + self.sb_rules(), + self.sb_stats()] %} {% endif %} diff --git a/amanuensis/server/lexicon/__init__.py b/amanuensis/server/lexicon/__init__.py index 412949d..56929b7 100644 --- a/amanuensis/server/lexicon/__init__.py +++ b/amanuensis/server/lexicon/__init__.py @@ -1,96 +1,82 @@ -from flask import ( - Blueprint, - flash, - redirect, - url_for, - g, - render_template, - Markup) +from flask import Blueprint, flash, redirect, url_for, g, render_template, Markup from flask_login import login_required, current_user -from amanuensis.lexicon import ( - player_can_join_lexicon, - add_player_to_lexicon, - sort_by_index_spec) -from amanuensis.models import LexiconModel -from amanuensis.server.helpers import ( - lexicon_param, - player_required_if_not_public) +from amanuensis.backend import lexiq, memq +from amanuensis.db import DbContext, Lexicon, User +from amanuensis.errors import ArgumentError +from amanuensis.server.helpers import lexicon_param, player_required_if_not_public from .forms import LexiconJoinForm -bp_lexicon = Blueprint('lexicon', __name__, - url_prefix='/lexicon/- {% for citation in article.cites %} - {{ citation }}{% if not loop.last %} / {% endif %} - {% endfor %} + {% for citation in article.cites %} + {{ citation }}{% if not loop.last %} / {% endif %} + {% endfor %}
- {% for citation in article.citedby %} - {{ citation }}{% if not loop.last %} / {% endif %} - {% endfor %} + {% for citation in article.citedby %} + {{ citation }}{% if not loop.last %} / {% endif %} + {% endfor %}
{% endblock %} diff --git a/amanuensis/server/lexicon/lexicon.contents.jinja b/amanuensis/server/lexicon/lexicon.contents.jinja index 811ddf6..a61e87c 100644 --- a/amanuensis/server/lexicon/lexicon.contents.jinja +++ b/amanuensis/server/lexicon/lexicon.contents.jinja @@ -14,7 +14,7 @@- {{ lexicon.full_title }} + {{ lexicon.full_title }} [{{ status.capitalize() }}]
@@ -29,7 +29,7 @@ Players: {{ lexicon.memberships|count }}{% if lexicon.player_limit is not none %} / {{ lexicon.player_limit }}{% endif -%} {%- if lexicon.public and lexicon.joinable - %} / Join game + %} / Join game {%- endif -%} {%- endif -%} diff --git a/mypy.ini b/mypy.ini index df16a93..5ef7afc 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,4 +1,4 @@ [mypy] ignore_missing_imports = true -exclude = "|amanuensis/lexicon/.*|amanuensis/server/.*|amanuensis/server/lexicon/.*|amanuensis/server/session/.*|" +exclude = "|amanuensis/lexicon/.*|amanuensis/server/.*|amanuensis/server/session/.*|" ; mypy stable doesn't support pyproject.toml yet \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index c05f21e..212e279 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,11 +22,11 @@ amanuensis-cli = "amanuensis.cli:main" amanuensis-server = "amanuensis.server:run" [tool.black] -extend-exclude = "^/amanuensis/lexicon/.*|^/amanuensis/server/[^/]*py|^/amanuensis/server/lexicon/.*|^/amanuensis/server/session/.*|" +extend-exclude = "^/amanuensis/lexicon/.*|^/amanuensis/server/[^/]*py|^/amanuensis/server/session/.*|" [tool.mypy] ignore_missing_imports = true -exclude = "|amanuensis/lexicon/.*|amanuensis/server/.*|amanuensis/server/lexicon/.*|amanuensis/server/session/.*|" +exclude = "|amanuensis/lexicon/.*|amanuensis/server/.*|amanuensis/server/session/.*|" [tool.pytest.ini_options] addopts = "--show-capture=log"