Get home page and login working #14

Merged
Jaculabilis merged 20 commits from tvb/server-auth into develop 2021-06-29 03:23:59 +00:00
12 changed files with 17 additions and 375 deletions
Showing only changes of commit 10a0c59d5e - Show all commits

View File

@ -1,99 +0,0 @@
# Standard library imports
import argparse
import logging
import os
import sys
# Module imports
from amanuensis.cli import describe_commands, get_commands
from amanuensis.config import (
RootConfigDirectoryContext,
ENV_CONFIG_DIR,
ENV_LOG_FILE)
from amanuensis.errors import AmanuensisError
from amanuensis.log import init_logging
from amanuensis.models import ModelFactory
def process_doc(docstring):
return '\n'.join([
line.strip()
for line in (docstring or "").strip().splitlines()
])
def get_parser(valid_commands):
# Set up the top-level parser.
parser = argparse.ArgumentParser(
description=describe_commands(),
formatter_class=argparse.RawDescriptionHelpFormatter)
# The config directory.
parser.add_argument("--config-dir",
dest="config_dir",
default=os.environ.get(ENV_CONFIG_DIR, "./config"),
help="The config directory for Amanuensis")
# Logging settings.
parser.add_argument("--verbose", "-v",
action="store_true",
dest="verbose",
help="Enable verbose console logging")
parser.add_argument("--log-file",
dest="log_file",
default=os.environ.get(ENV_LOG_FILE),
help="Enable verbose file logging")
parser.set_defaults(func=lambda args: parser.print_help())
subp = parser.add_subparsers(
metavar="COMMAND",
dest="command",
help="The command to execute")
# Set up command subparsers.
# command_ functions perform setup or execution depending on
# whether their argument is an ArgumentParser.
for name, func in valid_commands.items():
# Create the subparser, set the docstring as the description.
cmd = subp.add_parser(name,
description=process_doc(func.__doc__),
formatter_class=argparse.RawDescriptionHelpFormatter,
aliases=func.__dict__.get("aliases", []))
# Delegate subparser setup to the command.
func(cmd)
# Store function for later execution.
cmd.set_defaults(func=func)
return parser
def main(argv):
# Enumerate valid commands from the CLI module.
commands = get_commands()
# Parse args
args = get_parser(commands).parse_args(argv)
# First things first, initialize logging
init_logging(args.verbose, args.log_file)
logger = logging.getLogger('amanuensis')
# The init command initializes a config directory at --config-dir.
# All other commands assume that the config dir already exists.
if args.command and args.command != "init":
args.root = RootConfigDirectoryContext(args.config_dir)
args.model_factory = ModelFactory(args.root)
# If verbose logging, dump args namespace
if args.verbose:
logger.debug('amanuensis')
for key, val in vars(args).items():
logger.debug(f' {key}: {val}')
# Execute command.
try:
args.func(args)
except AmanuensisError as e:
logger.error('Unexpected internal {}: {}'.format(
type(e).__name__, str(e)))
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))

View File

@ -1,104 +0,0 @@
"""
Submodule of functions for creating and managing lexicons within the
general Amanuensis context.
"""
import json
import logging
import os
import re
import time
from typing import Iterable
import uuid
from amanuensis.config import RootConfigDirectoryContext, AttrOrderedDict
from amanuensis.errors import ArgumentError
from amanuensis.models import ModelFactory, UserModel, LexiconModel
from amanuensis.resources import get_stream
logger = logging.getLogger(__name__)
def valid_name(name: str) -> bool:
"""
Validates that a lexicon name consists only of alpahnumerics, dashes,
underscores, and spaces
"""
return re.match(r'^[A-Za-z0-9-_ ]+$', name) is not None
def create_lexicon(
root: RootConfigDirectoryContext,
name: str,
editor: UserModel) -> LexiconModel:
"""
Creates a lexicon with the given name and sets the given user as its editor
"""
# Verify arguments
if not name:
raise ArgumentError(f'Empty lexicon name: "{name}"')
if not valid_name(name):
raise ArgumentError(f'Invalid lexicon name: "{name}"')
with root.lexicon.read_index() as extant_lexicons:
if name in extant_lexicons.keys():
raise ArgumentError(f'Lexicon name already taken: "{name}"')
if editor is None:
raise ArgumentError('Editor must not be None')
# Create the lexicon directory and initialize it with a blank lexicon
lid: str = uuid.uuid4().hex
lex_dir = os.path.join(root.lexicon.path, lid)
os.mkdir(lex_dir)
with get_stream("lexicon.json") as s:
path: str = os.path.join(lex_dir, 'config.json')
with open(path, 'wb') as f:
f.write(s.read())
# Create subdirectories
os.mkdir(os.path.join(lex_dir, 'draft'))
os.mkdir(os.path.join(lex_dir, 'src'))
os.mkdir(os.path.join(lex_dir, 'article'))
# Update the index with the new lexicon
with root.lexicon.edit_index() as index:
index[name] = lid
# Fill out the new lexicon
with root.lexicon[lid].edit_config() as cfg:
cfg.lid = lid
cfg.name = name
cfg.editor = editor.uid
cfg.time.created = int(time.time())
with root.lexicon[lid].edit('info', create=True):
pass # Create an empry config file
# Load the lexicon and add the editor and default character
model_factory: ModelFactory = ModelFactory(root)
lexicon = model_factory.lexicon(lid)
with lexicon.ctx.edit_config() as cfg:
cfg.join.joined.append(editor.uid)
with get_stream('character.json') as template:
character = json.load(template, object_pairs_hook=AttrOrderedDict)
character.cid = 'default'
character.name = 'Ersatz Scrivener'
character.player = None
cfg.character.new(character.cid, character)
# Log the creation
message = f'Created {lexicon.title}, ed. {editor.cfg.displayname} ({lid})'
lexicon.log(message)
logger.info(message)
return lexicon
def load_all_lexicons(
root: RootConfigDirectoryContext) -> Iterable[LexiconModel]:
"""
Iterably loads every lexicon in the config store
"""
model_factory: ModelFactory = ModelFactory(root)
with root.lexicon.read_index() as index:
for lid in index.values():
lexicon: LexiconModel = model_factory.lexicon(lid)
yield lexicon

View File

@ -1,13 +0,0 @@
{
"version": "0",
"aid": null,
"lexicon": null,
"character": null,
"title": null,
"turn": null,
"status": {
"ready": false,
"approved": false
},
"contents": null
}

View File

@ -1,7 +0,0 @@
{
"version": "0",
"cid": null,
"name": null,
"player": null,
"signature": null
}

View File

@ -1,15 +0,0 @@
{
"version": "0",
"secret_key": null,
"address": "127.0.0.1",
"port": "5000",
"lexicon_data": "./lexicon",
"static_root": "../resources",
"email": {
"server": null,
"port": null,
"tls": null,
"username": null,
"password": null
}
}

View File

@ -1,107 +0,0 @@
{
"version": "0",
"lid": null,
"name": null,
"title": null,
"editor": null,
"prompt": null,
"time": {
"created": null,
"completed": null
},
"turn": {
"current": null,
"max": 8,
"assignment": {
}
},
"join": {
"public": false,
"open": false,
"password": null,
"max_players": 4,
"chars_per_player": 1,
"joined": []
},
"publish": {
"notify": {
"editor_on_ready": true,
"player_on_reject": true,
"player_on_accept": false
},
"deadlines": null,
"asap": false,
"quorum": null,
"block_on_ready": true
},
"article": {
"index": {
"list": [
{
"type": "char",
"pri": 0,
"pattern": "ABC"
},
{
"type": "char",
"pri": 0,
"pattern": "DEF"
},
{
"type": "char",
"pri": 0,
"pattern": "GHI"
},
{
"type": "char",
"pri": 0,
"pattern": "JKL"
},
{
"type": "char",
"pri": 0,
"pattern": "MNO"
},
{
"type": "char",
"pri": 0,
"pattern": "PQRS"
},
{
"type": "char",
"pri": 0,
"pattern": "TUV"
},
{
"type": "char",
"pri": 0,
"pattern": "WXYZ"
}
],
"capacity": null
},
"citation": {
"allow_self": false,
"min_extant": null,
"max_extant": null,
"min_phantom": null,
"max_phantom": null,
"min_total": null,
"max_total": null,
"min_chars": null,
"max_chars": null
},
"word_limit": {
"soft": null,
"hard": null
},
"addendum": {
"allowed": false,
"max": null
}
},
"character": {
},
"log": [
]
}

View File

@ -1,13 +0,0 @@
{
"version": "0",
"uid": null,
"username": null,
"displayname": null,
"email": null,
"password": null,
"created": null,
"last_login": null,
"last_activity": null,
"new_password_required": true,
"is_admin": false
}

View File

@ -10,7 +10,7 @@ bp = Blueprint("home", __name__, url_prefix="/home", template_folder=".")
@bp.get("/") @bp.get("/")
def home(): def home():
return render_template('home.root.jinja') return render_template("home.root.jinja")
@bp.get("/admin/") @bp.get("/admin/")

View File

@ -2,16 +2,16 @@ from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, TextAreaField from wtforms import StringField, SubmitField, TextAreaField
from wtforms.validators import DataRequired from wtforms.validators import DataRequired
from amanuensis.server.forms import User, Lexicon # from amanuensis.server.forms import User, Lexicon
class LexiconCreateForm(FlaskForm): # class LexiconCreateForm(FlaskForm):
"""/admin/create/""" # """/admin/create/"""
lexiconName = StringField( # lexiconName = StringField(
'Lexicon name', # 'Lexicon name',
validators=[DataRequired(), Lexicon(should_exist=False)]) # validators=[DataRequired(), Lexicon(should_exist=False)])
editorName = StringField( # editorName = StringField(
'Username of editor', # 'Username of editor',
validators=[DataRequired(), User(should_exist=True)]) # validators=[DataRequired(), User(should_exist=True)])
promptText = TextAreaField('Prompt') # promptText = TextAreaField('Prompt')
submit = SubmitField('Create') # submit = SubmitField('Create')

View File

@ -1,4 +1,4 @@
[mypy] [mypy]
ignore_missing_imports = true ignore_missing_imports = true
exclude = "|amanuensis/lexicon/.*|amanuensis/models/.*|amanuensis/resources/.*|amanuensis/server/.*|amanuensis/user/.*|amanuensis/__main__.py|" exclude = "|amanuensis/lexicon/.*|amanuensis/server/.*|amanuensis/server/lexicon/.*|amanuensis/server/session/.*|"
; mypy stable doesn't support pyproject.toml yet ; mypy stable doesn't support pyproject.toml yet

View File

@ -22,11 +22,11 @@ amanuensis-cli = "amanuensis.cli:main"
amanuensis-server = "amanuensis.server:run" amanuensis-server = "amanuensis.server:run"
[tool.black] [tool.black]
extend-exclude = "^/amanuensis/lexicon/.*|^/amanuensis/models/.*|^/amanuensis/resources/.*|^/amanuensis/server/.*|^/amanuensis/user/.*|^/amanuensis/__main__.py" extend-exclude = "^/amanuensis/lexicon/.*|^/amanuensis/server/[^/]*py|^/amanuensis/server/lexicon/.*|^/amanuensis/server/session/.*|"
[tool.mypy] [tool.mypy]
ignore_missing_imports = true ignore_missing_imports = true
exclude = "amanuensis/cli/.*|amanuensis/config/.*|amanuensis/lexicon/.*|amanuensis/log/.*|amanuensis/models/.*|amanuensis/resources/.*|amanuensis/server/.*|amanuensis/user/.*|amanuensis/__main__.py" exclude = "|amanuensis/lexicon/.*|amanuensis/server/.*|amanuensis/server/lexicon/.*|amanuensis/server/session/.*|"
[tool.pytest.ini_options] [tool.pytest.ini_options]
addopts = "--show-capture=log" addopts = "--show-capture=log"