diff --git a/editor/server/app/data/.gitignore b/editor/server/app/data/.gitignore index 8155a7e..42a332e 100644 --- a/editor/server/app/data/.gitignore +++ b/editor/server/app/data/.gitignore @@ -1 +1,2 @@ -cf-colos.json \ No newline at end of file +cf-colos.json +examples/ \ No newline at end of file diff --git a/editor/server/app/editor.py b/editor/server/app/editor.py index e7d6933..68ec96c 100644 --- a/editor/server/app/editor.py +++ b/editor/server/app/editor.py @@ -1,14 +1,11 @@ # SPDX-License-Identifier: MIT -import os - +from os import abort from flask import ( Blueprint, send_from_directory, ) - -from . import root_dir -default_res_folder = os.path.join(root_dir, 'editor/web/dist') +from . import static_dir bp = Blueprint('editor', __name__, url_prefix='/') @@ -16,4 +13,6 @@ bp = Blueprint('editor', __name__, url_prefix='/') @bp.route('/', defaults={'path': 'index.html'}) @bp.route('/') def index(path: str): - return send_from_directory(default_res_folder, path) + if not static_dir: + abort(500) + return send_from_directory(static_dir, path) diff --git a/editor/server/app/examples.py b/editor/server/app/examples.py index d0a128f..c557dcf 100644 --- a/editor/server/app/examples.py +++ b/editor/server/app/examples.py @@ -2,6 +2,7 @@ import copy import os +from pathlib import Path import re from flask import ( @@ -16,14 +17,15 @@ from .utils import ( render_extended_template, ) -from . import root_dir -examples_dir = os.path.join(root_dir, 'examples') -# TODO: copy to current folder for packaging - bp = Blueprint('examples', __name__, url_prefix='/') +examples_dir = Path(__file__).parent / 'data' / 'examples' param_cache: dict[str, dict] = {} +if not os.path.exists(examples_dir): + print('"example" directory does not exist. Run "hatch build" to generate.') + exit(1) + def get_page_params(name: str) -> ErrorPageParams: name = re.sub(r'[^\w]', '', name) @@ -39,12 +41,14 @@ def get_page_params(name: str) -> ErrorPageParams: return None -@bp.route('/', defaults={'name': 'default'}) +@bp.route('/') @bp.route('/') -def index(name: str): +def index(name: str = ''): lower_name = name.lower() - if name != lower_name: - return redirect(lower_name) + if lower_name == '': + return redirect('default', code=301) + elif name != lower_name: + return redirect(lower_name, code=301) else: name = lower_name diff --git a/editor/server/app/share.py b/editor/server/app/share.py index e8840fa..5b67098 100644 --- a/editor/server/app/share.py +++ b/editor/server/app/share.py @@ -11,7 +11,6 @@ from flask import ( current_app, request, abort, - jsonify, redirect, url_for, ) @@ -45,10 +44,10 @@ def create(): # Simple CSRF check sec_fetch_site = request.headers.get('Sec-Fetch-Site') if sec_fetch_site is not None and sec_fetch_site != 'same-origin': - return jsonify({ + return { 'status': 'failed', 'message': 'CSRF check failed (Sec-Fetch-Site)', - }), 403 + }, 403 # Accessing request.json raises 415 error if Content-Type is not application/json. This also prevents CSRF requests. # See https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/CSRF#avoiding_simple_requests @@ -64,15 +63,15 @@ def create(): db.session.commit() except: db.session.rollback() - return jsonify({ + return { 'status': 'failed', - }) - return jsonify({ + } + return { 'status': 'ok', 'name': item.name, 'url': request.host_url[:-1] + url_for('share_short.get', name=item.name), # TODO: better way to handle this - }) + } @bp_short.get('/') @@ -83,9 +82,9 @@ def get(name: str): item = db.session.query(models.Item).filter_by(name=name).first() if not item: if is_json: - return jsonify({ + return { 'status': 'notfound' - }) + } else: return abort(404) params = cast(ErrorPageParams, item.params) @@ -94,10 +93,10 @@ def get(name: str): params.pop('client_ip', None) if is_json: - return jsonify({ + return { 'status': 'ok', 'parameters': params, - }) + } else: params['creator_info'] = { 'hidden': False, diff --git a/editor/server/config.example.toml b/editor/server/config.example.toml index baadc68..5cf9c48 100644 --- a/editor/server/config.example.toml +++ b/editor/server/config.example.toml @@ -1,3 +1,6 @@ +# Directory of static files for editor (.html, .js, ...), relative to instance dir +STATIC_DIR = '../../web/dist' + # Url prefix for app urls URL_PREFIX = '' diff --git a/editor/server/hatch_build.py b/editor/server/hatch_build.py new file mode 100644 index 0000000..4cedf1f --- /dev/null +++ b/editor/server/hatch_build.py @@ -0,0 +1,17 @@ +import glob +import os +import shutil +from pathlib import Path +from typing import Any + +from hatchling.builders.hooks.plugin.interface import BuildHookInterface + + +class CustomBuildHook(BuildHookInterface): + def initialize(self, version: str, build_data: dict[str, Any]): + src = Path(self.root).parent.parent / 'examples' / '*.json' + dst = Path(self.root) / 'app' / 'data' / 'examples' + os.makedirs(dst, exist_ok=True) + for file in glob.glob(str(src)): + print(f'Copy {file} to {dst}') + shutil.copy(file, dst) diff --git a/editor/server/pyproject.toml b/editor/server/pyproject.toml index f65e3f3..7b20d19 100644 --- a/editor/server/pyproject.toml +++ b/editor/server/pyproject.toml @@ -21,3 +21,6 @@ allow-direct-references = true [tool.hatch.build] include = ["app/**/*"] + +[tool.hatch.build.targets.wheel.hooks.custom] +path = "hatch_build.py" \ No newline at end of file