Compare commits

...

3 Commits

10 changed files with 160 additions and 132 deletions

View File

@ -3,6 +3,7 @@ Lexicon query interface
""" """
import re import re
from typing import Sequence
from sqlalchemy import select, func from sqlalchemy import select, func
@ -52,3 +53,8 @@ def create(
db.session.add(new_lexicon) db.session.add(new_lexicon)
db.session.commit() db.session.commit()
return new_lexicon return new_lexicon
def get_all_lexicons(db: DbContext) -> Sequence[Lexicon]:
"""Get all lexicons."""
return db(select(Lexicon)).scalars()

View File

@ -3,7 +3,7 @@ User query interface
""" """
import re import re
import uuid from typing import Sequence
from sqlalchemy import select, func from sqlalchemy import select, func
@ -67,3 +67,8 @@ def create(
db.session.add(new_user) db.session.add(new_user)
db.session.commit() db.session.commit()
return new_user return new_user
def get_all_users(db: DbContext) -> Sequence[User]:
"""Get all users."""
return db(select(User)).scalars()

View File

@ -9,7 +9,7 @@ class AmanuensisConfig:
# If CONFIG_FILE is defined, the config file it points to may override # If CONFIG_FILE is defined, the config file it points to may override
# config values defined on the config object itself. # config values defined on the config object itself.
CONFIG_FILE: Optional[str] = None CONFIG_FILE: Optional[str] = None
STATIC_ROOT: Optional[str] = "static" STATIC_ROOT: Optional[str] = "../resources"
SECRET_KEY: Optional[str] = "secret" SECRET_KEY: Optional[str] = "secret"
DATABASE_URI: Optional[str] = "sqlite:///:memory:" DATABASE_URI: Optional[str] = "sqlite:///:memory:"
TESTING: bool = False TESTING: bool = False

View File

@ -13,7 +13,6 @@ from sqlalchemy import (
ForeignKey, ForeignKey,
Integer, Integer,
String, String,
Table,
Text, Text,
text, text,
TypeDecorator, TypeDecorator,
@ -234,6 +233,14 @@ class Lexicon(ModelBase):
content_rules = relationship("ArticleContentRule", back_populates="lexicon") content_rules = relationship("ArticleContentRule", back_populates="lexicon")
posts = relationship("Post", back_populates="lexicon") posts = relationship("Post", back_populates="lexicon")
#######################
# Derived information #
#######################
@property
def full_title(self: "Lexicon") -> str:
return self.title if self.title else f"Lexicon {self.name}"
class Membership(ModelBase): class Membership(ModelBase):
""" """

View File

@ -1,9 +1,11 @@
import json import json
import os
from flask import Flask, g from flask import Flask, g
from amanuensis.config import AmanuensisConfig, CommandLineConfig from amanuensis.config import AmanuensisConfig, CommandLineConfig
from amanuensis.db import DbContext from amanuensis.db import DbContext
import amanuensis.server.home
def get_app( def get_app(
@ -18,8 +20,8 @@ def get_app(
app.config.from_object(config) app.config.from_object(config)
# If a config file is now specified, also load keys from there # If a config file is now specified, also load keys from there
if app.config.get("CONFIG_FILE", None): if config_path := app.config.get("CONFIG_FILE", None):
app.config.from_file(app.config["CONFIG_FILE"], json.load) app.config.from_file(os.path.abspath(config_path), json.load)
# Assert that all required config values are now set # Assert that all required config values are now set
for config_key in ("SECRET_KEY", "DATABASE_URI"): for config_key in ("SECRET_KEY", "DATABASE_URI"):
@ -33,11 +35,13 @@ def get_app(
# Make the database connection available to requests via g # Make the database connection available to requests via g
def db_setup(): def db_setup():
g.db = db g.db = db
app.before_request(db_setup) app.before_request(db_setup)
# Tear down the session on request teardown # Tear down the session on request teardown
def db_teardown(response_or_exc): def db_teardown(response_or_exc):
db.session.remove() db.session.remove()
app.teardown_appcontext(db_teardown) app.teardown_appcontext(db_teardown)
# Configure jinja options # Configure jinja options
@ -47,10 +51,11 @@ def get_app(
# TODO # TODO
# Register blueprints # Register blueprints
# TODO app.register_blueprint(amanuensis.server.home.bp)
def test(): def test():
return "Hello, world!" return "Hello, world!"
app.route("/")(test) app.route("/")(test)
return app return app
@ -59,4 +64,5 @@ def get_app(
def run(): def run():
"""Run the server, populating the config from the command line.""" """Run the server, populating the config from the command line."""
config = CommandLineConfig() config = CommandLineConfig()
get_app(config).run(debug=config.TESTING) app = get_app(config)
app.run(debug=app.testing)

View File

@ -1,64 +1,64 @@
from flask import Blueprint, render_template, redirect, url_for, current_app from flask import Blueprint, render_template, g
from flask_login import login_required, current_user
from amanuensis.config import RootConfigDirectoryContext # from flask import Blueprint, render_template, redirect, url_for, current_app
from amanuensis.lexicon import create_lexicon, load_all_lexicons # from flask_login import login_required, current_user
from amanuensis.models import UserModel, ModelFactory
from amanuensis.server.helpers import admin_required
from .forms import LexiconCreateForm import amanuensis.backend.user as userq
import amanuensis.backend.lexicon as lexiq
bp_home = Blueprint('home', __name__, # from amanuensis.config import RootConfigDirectoryContext
url_prefix='/home', # from amanuensis.lexicon import create_lexicon, load_all_lexicons
template_folder='.') # from amanuensis.models import UserModel, ModelFactory
# from amanuensis.server.helpers import admin_required
# from .forms import LexiconCreateForm
bp = Blueprint("home", __name__, url_prefix="/home", template_folder=".")
@bp_home.route('/', methods=['GET']) # @bp.get("/")
def home(): # def home():
root: RootConfigDirectoryContext = current_app.config['root'] # Show lexicons that are visible to the current user
user: UserModel = current_user # return "TODO"
user_lexicons = [] # user_lexicons = []
public_lexicons = [] # public_lexicons = []
for lexicon in load_all_lexicons(root): # for lexicon in load_all_lexicons(root):
if user.uid in lexicon.cfg.join.joined: # if user.uid in lexicon.cfg.join.joined:
user_lexicons.append(lexicon) # user_lexicons.append(lexicon)
elif lexicon.cfg.join.public: # elif lexicon.cfg.join.public:
public_lexicons.append(lexicon) # public_lexicons.append(lexicon)
return render_template( # return render_template(
'home.root.jinja', # 'home.root.jinja',
user_lexicons=user_lexicons, # user_lexicons=user_lexicons,
public_lexicons=public_lexicons) # public_lexicons=public_lexicons)
@bp_home.route('/admin/', methods=['GET']) @bp.get("/admin/")
@login_required # @login_required
@admin_required # @admin_required
def admin(): def admin():
root: RootConfigDirectoryContext = current_app.config['root'] return render_template("home.admin.jinja", db=g.db, userq=userq, lexiq=lexiq)
users = []
lexicons = list(load_all_lexicons(root))
return render_template('home.admin.jinja', users=users, lexicons=lexicons)
@bp_home.route("/admin/create/", methods=['GET', 'POST']) # @bp_home.route("/admin/create/", methods=['GET', 'POST'])
@login_required # @login_required
@admin_required # @admin_required
def admin_create(): # def admin_create():
form = LexiconCreateForm() # form = LexiconCreateForm()
if not form.validate_on_submit(): # if not form.validate_on_submit():
# GET or POST with invalid form data # # GET or POST with invalid form data
return render_template('home.create.jinja', form=form) # return render_template('home.create.jinja', form=form)
# POST with valid data # # POST with valid data
root: RootConfigDirectoryContext = current_app.config['root'] # root: RootConfigDirectoryContext = current_app.config['root']
model_factory: ModelFactory = current_app.config['model_factory'] # model_factory: ModelFactory = current_app.config['model_factory']
lexicon_name = form.lexiconName.data # lexicon_name = form.lexiconName.data
editor_name = form.editorName.data # editor_name = form.editorName.data
prompt = form.promptText.data # prompt = form.promptText.data
# Editor's existence was checked by form validators # # Editor's existence was checked by form validators
editor = model_factory.user(editor_name) # editor = model_factory.user(editor_name)
lexicon = create_lexicon(root, lexicon_name, editor) # lexicon = create_lexicon(root, lexicon_name, editor)
with lexicon.ctx.edit_config() as cfg: # with lexicon.ctx.edit_config() as cfg:
cfg.prompt = prompt # cfg.prompt = prompt
return redirect(url_for('session.session', name=lexicon_name)) # return redirect(url_for('session.session', name=lexicon_name))

View File

@ -3,17 +3,18 @@
{% block title %}Admin | Amanuensis{% endblock %} {% block title %}Admin | Amanuensis{% endblock %}
{% block header %}<h2>Amanuensis - Admin Dashboard</h2>{% endblock %} {% block header %}<h2>Amanuensis - Admin Dashboard</h2>{% endblock %}
{% block sb_home %}<a href="{{ url_for('home.home') }}">Home</a>{% endblock %} {# TODO #}
{% block sb_create %}<a href="{{ url_for('home.admin_create') }}">Create a lexicon</a>{% endblock %} {% block sb_home %}<a href="#{#{ url_for('home.home') }#}">Home</a>{% endblock %}
{% block sb_create %}<a href="#{#{ url_for('home.admin_create') }#}">Create a lexicon</a>{% endblock %}
{% set template_sidebar_rows = [self.sb_home(), self.sb_create()] %} {% set template_sidebar_rows = [self.sb_home(), self.sb_create()] %}
{% block main %} {% block main %}
<p>Users:</p> <p>Users:</p>
{% for user in users %} {% for user in userq.get_all_users(db) %}
{{ macros.dashboard_user_item(user) }} {{ macros.dashboard_user_item(user) }}
{% endfor %} {% endfor %}
<p>Lexicons:</p> <p>Lexicons:</p>
{% for lexicon in lexicons %} {% for lexicon in lexiq.get_all_lexicons(db) %}
{{ macros.dashboard_lexicon_item(lexicon) }} {{ macros.dashboard_lexicon_item(lexicon) }}
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}

View File

@ -1,45 +1,47 @@
{% macro dashboard_lexicon_item(lexicon) %} {% macro dashboard_lexicon_item(lexicon) %}
<div class="dashboard-lexicon-item dashboard-lexicon-{{ lexicon.status }}"> {% set status = "completed" if lexicon.completed else "ongoing" if lexicon.started else "unstarted" %}
<p> <div class="dashboard-lexicon-item dashboard-lexicon-{{ status }}">
<span class="dashboard-lexicon-item-title"> <p>
<a href="{{ url_for('lexicon.contents', name=lexicon.cfg.name) }}"> <span class="dashboard-lexicon-item-title">
Lexicon {{ lexicon.cfg.name }}</a> <a href="#{#{ url_for('lexicon.contents', name=lexicon.cfg.name) }#}">
</span> {{ lexicon.full_title }}</a>
[{{ lexicon.status.capitalize() }}] </span>
</p> [{{ lexicon.status.capitalize() }}]
<p><i>{{ lexicon.cfg.prompt }}</i></p> </p>
{% if current_user.is_authenticated %} <p><i>{{ lexicon.prompt }}</i></p>
<p> {# {% if current_user.is_authenticated %} #}
{% <p>
if current_user.uid in lexicon.cfg.join.joined {# TODO #}
or current_user.cfg.is_admin {# {%
%} if current_user.uid in lexicon.cfg.join.joined
Editor: {{ lexicon.cfg.editor|user_attr('username') }} / or current_user.cfg.is_admin
Players: %} #}
{% for uid in lexicon.cfg.join.joined %} Editor: {#{ lexicon.cfg.editor|user_attr('username') }#} /
{{ uid|user_attr('username') }}{% if not loop.last %}, {% endif %} Players:
{% endfor %} {# {% for uid in lexicon.cfg.join.joined %} #}
({{ lexicon.cfg.join.joined|count }}/{{ lexicon.cfg.join.max_players }}) {# {{ uid|user_attr('username') }}{% if not loop.last %}, {% endif %} #}
{% else %} {# {% endfor %} #}
Players: {{ lexicon.cfg.join.joined|count }}/{{ lexicon.cfg.join.max_players }} {# ({{ lexicon.cfg.join.joined|count }}/{{ lexicon.cfg.join.max_players }}) #}
{% if lexicon.cfg.join.public and lexicon.cfg.join.open %} {# {% else %} #}
/ <a href="{{ url_for('lexicon.join', name=lexicon.cfg.name) }}"> {# Players: {{ lexicon.cfg.join.joined|count }}/{{ lexicon.cfg.join.max_players }} #}
Join game {# {% if lexicon.cfg.join.public and lexicon.cfg.join.open %} #}
</a> {# / <a href="{{ url_for('lexicon.join', name=lexicon.cfg.name) }}"> #}
{% endif %} {# Join game #}
{% endif %} {# </a> #}
</p> {# {% endif %} #}
{% endif %} {# {% endif %} #}
</p>
{# {% endif %} #}
</div> </div>
{% endmacro %} {% endmacro %}
{% macro dashboard_user_item(user) %} {% macro dashboard_user_item(user) %}
<div class="dashboard-lexicon-item"> <div class="dashboard-lexicon-item">
<p> <p>
<b>{{ user.cfg.username }}</b> <b>{{ user.username }}</b>
{% if user.cfg.username != user.cfg.displayname %} / {{ user.cfg.displayname }}{% endif %} {% if user.username != user.display_name %} / {{ user.display_name }}{% endif %}
({{user.uid}}) (id #{{user.id}})
</p> </p>
<p>Last activity: {{ user.cfg.last_activity|asdate }} &mdash; Last login: {{ user.cfg.last_login|asdate }}</p> <p>Last activity: {{ user.last_activity }} &mdash; Last login: {{ user.last_login }}</p>
</div> </div>
{% endmacro %} {% endmacro %}

View File

@ -1,33 +1,34 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"/> <meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{% endblock %}</title> <title>{% block title %}{% endblock %}</title>
<link rel="icon" type="image/png" href="{{ url_for('static', filename='amanuensis.png') }}"> <link rel="icon" type="image/png" href="{{ url_for('static', filename='amanuensis.png') }}">
<link rel="stylesheet" href="{{ url_for("static", filename="page.css") }}"> <link rel="stylesheet" href="{{ url_for('static', filename='page.css') }}">
</head> </head>
<body> <body>
<div id="wrapper"> <div id="wrapper">
<div id="header"> <div id="header">
<div id="login-status" {% block login_status_attr %}{% endblock %}> <div id="login-status" {% block login_status_attr %}{% endblock %}>
{% if current_user.is_authenticated %} {# TODO #}
<b>{{ current_user.cfg.username -}}</b> {# {% if current_user.is_authenticated %}
(<a href="{{ url_for('auth.logout') }}">Logout</a>) <b>{{ current_user.cfg.username -}}</b>
{% else %} (<a href="{{ url_for('auth.logout') }}">Logout</a>)
<a href="{{ url_for('auth.login') }}">Login</a> {% else %} #}
{% endif %} <a href="#{#{ url_for('auth.login') }#}">Login</a>
</div> {# {% endif %} #}
{% block header %}{% endblock %} </div>
</div> {% block header %}{% endblock %}
{% block sidebar %}{% endblock %} </div>
<div id="content" class="{% block content_class %}{% endblock %}"> {% block sidebar %}{% endblock %}
{% if not template_content_blocks %}{% set template_content_blocks = [] %}{% endif %} <div id="content" class="{% block content_class %}{% endblock %}">
{% if not content_blocks %}{% set content_blocks = [] %}{% endif %} {% if not template_content_blocks %}{% set template_content_blocks = [] %}{% endif %}
{% for content_block in template_content_blocks + content_blocks %}<div class="contentblock"> {% if not content_blocks %}{% set content_blocks = [] %}{% endif %}
{{ content_block|safe }}</div> {% for content_block in template_content_blocks + content_blocks %}<div class="contentblock">
{% endfor %} {{ content_block|safe }}</div>
</div> {% endfor %}
</div> </div>
</div>
</body> </body>
</html> </html>

View File

@ -1,12 +1,12 @@
{% extends "page.jinja" %} {% extends "page.jinja" %}
{% block sidebar %} {% block sidebar %}
<div id="sidebar"> <div id="sidebar">
{% if not template_sidebar_rows %}{% set template_sidebar_rows = [] %}{% endif %} {% if not template_sidebar_rows %}{% set template_sidebar_rows = [] %}{% endif %}
{% if not sidebar_rows %}{% set sidebar_rows = [] %}{% endif %} {% if not sidebar_rows %}{% set sidebar_rows = [] %}{% endif %}
<table> <table>
{% for row in template_sidebar_rows + sidebar_rows %} {% for row in template_sidebar_rows + sidebar_rows %}
<tr><td>{{ row|safe }}</td></tr>{% endfor %} <tr><td>{{ row|safe }}</td></tr>{% endfor %}
</table> </table>
</div> </div>
{% endblock %} {% endblock %}
{% block content_class %}content-2col{% endblock %} {% block content_class %}content-2col{% endblock %}