mirror of https://github.com/lukechilds/lnbits.git
Browse Source
Flask extensions are loaded in a way that makes them easily reusable by blueprints. In this commit we are also adding `environs` to manage .env and settings: breaking changes! - FLASK_APP=lnbits.app - LNBITS_ALLOWED_USERS needs to be empty now to allow all users (NOT "all")aiosqlite
committed by
fiatjaf
13 changed files with 226 additions and 138 deletions
@ -1,116 +0,0 @@ |
|||
import re |
|||
import importlib |
|||
import sqlite3 |
|||
|
|||
from flask import Flask |
|||
from flask_assets import Environment, Bundle # type: ignore |
|||
from flask_compress import Compress # type: ignore |
|||
from flask_cors import CORS # type: ignore |
|||
from flask_talisman import Talisman # type: ignore |
|||
from os import getenv |
|||
from werkzeug.middleware.proxy_fix import ProxyFix |
|||
|
|||
from .core import core_app |
|||
from .helpers import ExtensionManager |
|||
from .settings import FORCE_HTTPS |
|||
from .db import open_db, open_ext_db |
|||
|
|||
|
|||
disabled_extensions = getenv("LNBITS_DISABLED_EXTENSIONS", "").split(",") |
|||
|
|||
app = Flask(__name__) |
|||
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1) # type: ignore |
|||
valid_extensions = [ext for ext in ExtensionManager(disabled=disabled_extensions).extensions if ext.is_valid] |
|||
|
|||
|
|||
# optimization & security |
|||
# ----------------------- |
|||
|
|||
Compress(app) |
|||
CORS(app) |
|||
Talisman( |
|||
app, |
|||
force_https=FORCE_HTTPS, |
|||
content_security_policy={ |
|||
"default-src": [ |
|||
"'self'", |
|||
"'unsafe-eval'", |
|||
"'unsafe-inline'", |
|||
"blob:", |
|||
"api.opennode.co", |
|||
] |
|||
}, |
|||
) |
|||
|
|||
|
|||
# blueprints / extensions |
|||
# ----------------------- |
|||
|
|||
app.register_blueprint(core_app) |
|||
|
|||
for ext in valid_extensions: |
|||
try: |
|||
ext_module = importlib.import_module(f"lnbits.extensions.{ext.code}") |
|||
app.register_blueprint(getattr(ext_module, f"{ext.code}_ext"), url_prefix=f"/{ext.code}") |
|||
except Exception: |
|||
raise ImportError(f"Please make sure that the extension `{ext.code}` follows conventions.") |
|||
|
|||
|
|||
# filters |
|||
# ------- |
|||
|
|||
app.jinja_env.globals["DEBUG"] = app.config["DEBUG"] |
|||
app.jinja_env.globals["EXTENSIONS"] = valid_extensions |
|||
app.jinja_env.globals["SITE_TITLE"] = getenv("LNBITS_SITE_TITLE", "LNbits") |
|||
|
|||
|
|||
# assets |
|||
# ------ |
|||
|
|||
assets = Environment(app) |
|||
assets.url = app.static_url_path |
|||
assets.register("base_css", Bundle("scss/base.scss", filters="pyscss", output="css/base.css")) |
|||
|
|||
|
|||
# commands |
|||
# -------- |
|||
|
|||
|
|||
def migrate_databases(): |
|||
"""Creates the necessary databases if they don't exist already; or migrates them.""" |
|||
|
|||
from .core import migrations as core_migrations |
|||
|
|||
with open_db() as core_db: |
|||
try: |
|||
rows = core_db.fetchall("SELECT * FROM dbversions") |
|||
except sqlite3.OperationalError: |
|||
# migration 3 wasn't ran |
|||
core_migrations.m000_create_migrations_table(core_db) |
|||
rows = core_db.fetchall("SELECT * FROM dbversions") |
|||
|
|||
current_versions = {row["db"]: row["version"] for row in rows} |
|||
matcher = re.compile(r"^m(\d\d\d)_") |
|||
|
|||
def run_migration(db, migrations_module): |
|||
db_name = migrations_module.__name__.split(".")[-2] |
|||
for key, run_migration in migrations_module.__dict__.items(): |
|||
match = match = matcher.match(key) |
|||
if match: |
|||
version = int(match.group(1)) |
|||
if version > current_versions.get(db_name, 0): |
|||
print(f"running migration {db_name}.{version}") |
|||
run_migration(db) |
|||
core_db.execute( |
|||
"INSERT OR REPLACE INTO dbversions (db, version) VALUES (?, ?)", (db_name, version) |
|||
) |
|||
|
|||
run_migration(core_db, core_migrations) |
|||
|
|||
for ext in valid_extensions: |
|||
try: |
|||
ext_migrations = importlib.import_module(f"lnbits.extensions.{ext.code}.migrations") |
|||
with open_ext_db(ext.code) as db: |
|||
run_migration(db, ext_migrations) |
|||
except ImportError: |
|||
raise ImportError(f"Please make sure that the extension `{ext.code}` has a migrations file.") |
@ -1,4 +1,8 @@ |
|||
from lnbits import app, migrate_databases |
|||
from .app import create_app |
|||
from .commands import migrate_databases |
|||
|
|||
|
|||
migrate_databases() |
|||
|
|||
app = create_app() |
|||
app.run() |
|||
|
@ -0,0 +1,75 @@ |
|||
import importlib |
|||
|
|||
from flask import Flask |
|||
from flask_assets import Bundle # type: ignore |
|||
from flask_cors import CORS # type: ignore |
|||
from flask_talisman import Talisman # type: ignore |
|||
from werkzeug.middleware.proxy_fix import ProxyFix |
|||
|
|||
from .commands import legacy_migrate |
|||
from .core import core_app |
|||
from .ext import assets, compress |
|||
from .helpers import get_valid_extensions |
|||
|
|||
|
|||
def create_app(config_object="lnbits.settings") -> Flask: |
|||
"""Create application factory, as explained here: http://flask.pocoo.org/docs/patterns/appfactories/. |
|||
:param config_object: The configuration object to use. |
|||
""" |
|||
app = Flask(__name__, static_folder="static") |
|||
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1) # type: ignore |
|||
app.config.from_object(config_object) |
|||
|
|||
register_flask_extensions(app) |
|||
register_blueprints(app) |
|||
register_filters(app) |
|||
register_commands(app) |
|||
|
|||
return app |
|||
|
|||
|
|||
def register_blueprints(app) -> None: |
|||
"""Register Flask blueprints / LNbits extensions.""" |
|||
app.register_blueprint(core_app) |
|||
|
|||
for ext in get_valid_extensions(): |
|||
try: |
|||
ext_module = importlib.import_module(f"lnbits.extensions.{ext.code}") |
|||
app.register_blueprint(getattr(ext_module, f"{ext.code}_ext"), url_prefix=f"/{ext.code}") |
|||
except Exception: |
|||
raise ImportError(f"Please make sure that the extension `{ext.code}` follows conventions.") |
|||
|
|||
|
|||
def register_commands(app): |
|||
"""Register Click commands.""" |
|||
app.cli.add_command(legacy_migrate) |
|||
|
|||
|
|||
def register_flask_extensions(app): |
|||
"""Register Flask extensions.""" |
|||
"""If possible we use the .init_app() option so that Blueprints can also use extensions.""" |
|||
CORS(app) |
|||
Talisman( |
|||
app, |
|||
force_https=app.config["FORCE_HTTPS"], |
|||
content_security_policy={ |
|||
"default-src": [ |
|||
"'self'", |
|||
"'unsafe-eval'", |
|||
"'unsafe-inline'", |
|||
"blob:", |
|||
"api.opennode.co", |
|||
] |
|||
}, |
|||
) |
|||
|
|||
assets.init_app(app) |
|||
assets.register("base_css", Bundle("scss/base.scss", filters="pyscss", output="css/base.css")) |
|||
compress.init_app(app) |
|||
|
|||
|
|||
def register_filters(app): |
|||
"""Jinja filters.""" |
|||
app.jinja_env.globals["DEBUG"] = app.config["DEBUG"] |
|||
app.jinja_env.globals["EXTENSIONS"] = get_valid_extensions() |
|||
app.jinja_env.globals["SITE_TITLE"] = app.config["LNBITS_SITE_TITLE"] |
@ -0,0 +1,51 @@ |
|||
import click |
|||
import importlib |
|||
import re |
|||
import sqlite3 |
|||
|
|||
from .core import migrations as core_migrations |
|||
from .db import open_db, open_ext_db |
|||
from .helpers import get_valid_extensions |
|||
|
|||
|
|||
@click.command("migrate") |
|||
def legacy_migrate(): |
|||
migrate_databases() |
|||
|
|||
|
|||
def migrate_databases(): |
|||
"""Creates the necessary databases if they don't exist already; or migrates them.""" |
|||
|
|||
with open_db() as core_db: |
|||
try: |
|||
rows = core_db.fetchall("SELECT * FROM dbversions") |
|||
except sqlite3.OperationalError: |
|||
# migration 3 wasn't ran |
|||
core_migrations.m000_create_migrations_table(core_db) |
|||
rows = core_db.fetchall("SELECT * FROM dbversions") |
|||
|
|||
current_versions = {row["db"]: row["version"] for row in rows} |
|||
matcher = re.compile(r"^m(\d\d\d)_") |
|||
|
|||
def run_migration(db, migrations_module): |
|||
db_name = migrations_module.__name__.split(".")[-2] |
|||
for key, run_migration in migrations_module.__dict__.items(): |
|||
match = match = matcher.match(key) |
|||
if match: |
|||
version = int(match.group(1)) |
|||
if version > current_versions.get(db_name, 0): |
|||
print(f"running migration {db_name}.{version}") |
|||
run_migration(db) |
|||
core_db.execute( |
|||
"INSERT OR REPLACE INTO dbversions (db, version) VALUES (?, ?)", (db_name, version) |
|||
) |
|||
|
|||
run_migration(core_db, core_migrations) |
|||
|
|||
for ext in get_valid_extensions(): |
|||
try: |
|||
ext_migrations = importlib.import_module(f"lnbits.extensions.{ext.code}.migrations") |
|||
with open_ext_db(ext.code) as db: |
|||
run_migration(db, ext_migrations) |
|||
except ImportError: |
|||
raise ImportError(f"Please make sure that the extension `{ext.code}` has a migrations file.") |
@ -0,0 +1,6 @@ |
|||
from flask_assets import Environment # type: ignore |
|||
from flask_compress import Compress # type: ignore |
|||
|
|||
|
|||
assets = Environment() |
|||
compress = Compress() |
@ -1,14 +1,26 @@ |
|||
import importlib |
|||
import os |
|||
|
|||
from environs import Env # type: ignore |
|||
from os import path |
|||
from typing import List |
|||
|
|||
|
|||
env = Env() |
|||
env.read_env() |
|||
|
|||
wallets_module = importlib.import_module("lnbits.wallets") |
|||
wallet_class = getattr(wallets_module, os.getenv("LNBITS_BACKEND_WALLET_CLASS", "VoidWallet")) |
|||
wallet_class = getattr(wallets_module, env.str("LNBITS_BACKEND_WALLET_CLASS", default="VoidWallet")) |
|||
|
|||
ENV = env.str("FLASK_ENV", default="production") |
|||
DEBUG = ENV == "development" |
|||
|
|||
LNBITS_PATH = os.path.dirname(os.path.realpath(__file__)) |
|||
LNBITS_DATA_FOLDER = os.getenv("LNBITS_DATA_FOLDER", os.path.join(LNBITS_PATH, "data")) |
|||
LNBITS_PATH = path.dirname(path.realpath(__file__)) |
|||
LNBITS_DATA_FOLDER = env.str("LNBITS_DATA_FOLDER", default=path.join(LNBITS_PATH, "data")) |
|||
LNBITS_ALLOWED_USERS: List[str] = env.list("LNBITS_ALLOWED_USERS", default=[], subcast=str) |
|||
LNBITS_DISABLED_EXTENSIONS: List[str] = env.list("LNBITS_DISABLED_EXTENSIONS", default=[], subcast=str) |
|||
LNBITS_SITE_TITLE = env.str("LNBITS_SITE_TITLE", default="LNbits") |
|||
|
|||
WALLET = wallet_class() |
|||
DEFAULT_WALLET_NAME = os.getenv("LNBITS_DEFAULT_WALLET_NAME", "LNbits wallet") |
|||
FORCE_HTTPS = os.getenv("LNBITS_FORCE_HTTPS", "1") == "1" |
|||
SERVICE_FEE = float(os.getenv("LNBITS_SERVICE_FEE", "0.0")) |
|||
DEFAULT_WALLET_NAME = env.str("LNBITS_DEFAULT_WALLET_NAME", default="LNbits wallet") |
|||
FORCE_HTTPS = env.bool("LNBITS_FORCE_HTTPS", default=True) |
|||
SERVICE_FEE = env.float("LNBITS_SERVICE_FEE", default=0.0) |
|||
|
Loading…
Reference in new issue