Lore is a platform for creating and sharing stories and fictional worlds between printed and digital media. It's a wiki and a tool for game creators and gamers alike. It's a responsive web based platform that should work equally well on desktop, tablet and mobiles (optimized for modern browsers - above IE8). However, it is built with an API to support non-web frontends such as mobile apps.
Lore's backend is built in Python, and we use the following frameworks (plus more):
- Flask: Mini-framework that provides the core HTTP functionality of taking requests, reading parameters, rendering an HTML template and responding to user.
- MongoDB: For database we use the NoSQL MongoDB that gives us a JSON like flexible document structure rather than fixed columns à la SQL.
- Flask-Mongoengine (package
flask.ext.mongoengine.wtf
): Comes with the automatic translation of Mongoengine Document objects (Model objects) into Forms by calling model_form() function, and also makes it easy to enhance an app with the Mongoengine layer. - WTForms (package
wtforms
): a generic library for building Form objects that can parse and validate data before filling up the data model object with it. - Flask-WTF (package
flask.ext.wtf
): a mini library that joins the WTForms with Flask, such as automatically adding CSRF tokens (security) - Bootstrap: for overall CSS design, typography and Javascript components
- jQuery: For Javascript components.
The Lore app contains five main components that interact to create the full application, but are otherwise relatively isolated. They are implemented as Flask Blueprints. They are:
- World: The biggest component for Lore, and involves Worlds and all Articles. It can be seen as a wiki or content management system optimized for fictional worlds, and with a semantical structure such as relations between places, persons, events, and so on.
- Social: Contains logic for users, game groups and conversations. Here users can follow other users, set up discussions or form themselves into gaming groups.
- Shop: Contains an order system, a storefront and shopping cart and purchasing logic.
- Campaign: This section allows players, mostly game masters, to manage their own campaigns, including scheduling game sessions, managing story lines and scenes, and so on.
The Flask app Lore
runs behind a WSGI webserver. It's a REST based API / URL hierarchy that both offers a JSON API supporting GET, POST, PUT, PATCH and DELETE as well as to a traditional rendered HTML GET and POST interface. Almost all HTML is pre-rendered on server and served to client, with minor Javascript added for usability. The general principle is that of "progressive enhancement" - the website should be readable by all types of browsers including search engines and screen readers.
At the top of the file hierarchy we have a lore
directory for application code, a tests
directory for test code and tools
for peripheral tools. Additionally, it includes the README and other main documentation, the script to start the server (run.py
) and the package dependencies (requirements.in
and dev-requirements.in
).
In the lore
folder we have the following parts of the source code:
app.py
- main app code to initialize the application and run itmodel/
- contains model definitions, e.g. Article, World, User, etc.api/
- each controller is a Blueprint in Flask, and initializes the URL routes, forms and request handlers. Relies heavily on resource.py.api/resource.py
- our own library for simplyfing the creation of the API and request flow for typical model objects with REST like operations supportedtemplates/
- sub-organized by blueprint, this contains all Jinja2 type HTML templates.translations/
- localization files.
Outside the lore
folder we also have a static
folder that contains generated JS, CSS and some image assets, plus a plugins
folder that contains themes and other plugins.
Prerequisites: you need to have a terminal with installed git.
First do git clone git@github.com:helmgast/lore.git
and cd into that directory.
Then, decide if you want to run locally using virtualenv for python or if you want to use Docker to run a containerized application.
Run npm install
to install all frontend (JS, CSS) dependencies. You need npm installed.
Run npm run build
to do one-time production build (which will create new versioned assets in /static/ that needs to be commited to repo)
Run npm run watch
when developing, will continuously re-build frontend assets as they change.
Running virtualenv manages your python dependencies in isolation from your development machine, but still needs you to manage other parts like database yourself
- You need to have python >3.7, pip, pip-tools and virtualenv installed. Additionally, to run the database locally, you need to install mongodb 4.0 or higher.
- Inside the directory, execute:
virtualenv venv
. It creates the sub-directoryvenv
. Activate it by runningsource venv/bin/activate
(needs to be done every time you want to develop) - The
requirements.in
anddev-requirements.in
files contain the dependencies we have, with a rule to search for specific version or latest if empty. Runpip-compile requirements.in dev-requirements.in
to build them intorequirements.txt dev-requirements.txt
that contain the actual, locked versions, similar to howpackage-lock.json
works. This file guarantees that a Docker container or clone of the repo can install the exact same dependencies. Install those withpip install -r requirements.txt
for production orpip install -r requirements.txt -r dev-requirements.txt
for all. - In a separate terminal, run MongoDB by executing
mongod
. Default config will assume adefaultdb
on your MongoDB localhost. If that is ok, you are done, otherwise create a localconfig.py
in thelore/
directory, and override any settings needed fromdefault_config.py
.config.py
should be written without any classes. - Set an environment with FLASK_APP=run.py and LORE_DEBUG=True
- Start the development server by running
flask run
. flask command docs
Running Docker means having a Virtual Machine and/or separate user space where you can run the full application, including database, with guaranteed separation from your local machine, as well as (almost) identical to the server environment
- Install Docker if not already
- Run a temporary database image:
docker run -d --name mongo -e MONGO_INITDB_ROOT_USERNAME=admin -e MONGO_INITDB_ROOT_PASSWORD=pass -p 27018:27017 mongo
. Important to setup auth to keep it similar to how you'd run it in production. - Build the image by running
docker build -t lore .
This uses theDockerfile
to setup the correct dependencies and environment. Rebuild any time your code has changed. - Start the image by running
docker run -it --rm --link mongo:mongo lore
(--rm will remove the containers after exiting, skip that option if you want to have persistence).
(alternatively, use docker-compose and create a `docker-compose.yml' file that can include both database, application and other services)
There are 3 levels of debugging that can be done.
- Run the Flask development server on local machine, started with
flask run
or from your IDE. It will need a MongoDB instance running, and can be reached in browser using localhost:5000 . Note, this will NOT easily support the multi-domain behaviour of Lore. - Run the docker-compose file to setup an Nginx reverse proxy. This will let you run as close to production environment as possible, including with (self-signed) HTTPS and multiple domains. Lore listens to lore.test by default. You need to create local pointers to lore.test and other .test domains (for publishers) in /etc/hosts or using local DNS. When you do this, you can point Nginx to your Flask development server as started above, where you can debug and set breakpoints.
- If you don't need to set breakpoints, just run the full docker-compose file to start the full environment from Docker. This will run lore on the full configuration.
Lore defaults to English but comes with a Swedish localization as well, and more can be added. If you add or change new messages to translate, see below:
- Run
python manage.py lang_extract
to find all strings needing translations and update the translation file. - Search the lore/translations/[locale]/messages.po file for strings without translations, and translate in that file according to the format.
- Run
python manage.py lang_compile
to build the binary file that is then used to do the actual translation during runtime. (note, the binary file is not added to repository so you need to compile the language on each update and host). If you run Docker, the language will be automatically compiled at Docker build time.
For deployment on a production server, it's recommended to use a Docker setup with gunicorn
as WSGI server and potentially nginx
as reverse proxy and cache. This repository contain no deployment details or settings for security, see separate repository.
To keep it simple, static assets will be served either by flasks static
route or by FileAssets
. To work well in production, you need to have a cache in front of these - we use nginx. Setup nginx as proxy cache, and it will serve files much more efficiently (except at first request).
If you do not use a cache in front, you could move/copy the static assets from /static folder to a place where a more efficient web server can serve them. But this would not cover the FileAsset
case as they need to be served from Lore as it uses Mongonegine as the file backend, not a file system.