mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-17 05:54:19 +01:00
Add code to support integration with a Vite build system for JS/CSS (#2051)
* Adds the `Assets` constant to access front end assets from Jinja templates * Adds a `views.themes_beta` route to avoid the `.dev`/`.min` extension being added automatically to frontend asset urls * Add `count` meta field to `/api/v1/users/me/solves`, `/api/v1/users/me/fails`, `/api/v1/users/me/awards`, `/api/v1/users/[user_id]/solves`, `/api/v1/users/[user_id]/fails`, `/api/v1/users/[user_id]/awards` * Works on #2049
This commit is contained in:
@@ -339,7 +339,8 @@ class UserPrivateSolves(Resource):
|
|||||||
if response.errors:
|
if response.errors:
|
||||||
return {"success": False, "errors": response.errors}, 400
|
return {"success": False, "errors": response.errors}, 400
|
||||||
|
|
||||||
return {"success": True, "data": response.data}
|
count = len(response.data)
|
||||||
|
return {"success": True, "data": response.data, "meta": {"count": count}}
|
||||||
|
|
||||||
|
|
||||||
@users_namespace.route("/me/fails")
|
@users_namespace.route("/me/fails")
|
||||||
@@ -358,8 +359,8 @@ class UserPrivateFails(Resource):
|
|||||||
data = response.data
|
data = response.data
|
||||||
else:
|
else:
|
||||||
data = []
|
data = []
|
||||||
count = len(response.data)
|
|
||||||
|
|
||||||
|
count = len(response.data)
|
||||||
return {"success": True, "data": data, "meta": {"count": count}}
|
return {"success": True, "data": data, "meta": {"count": count}}
|
||||||
|
|
||||||
|
|
||||||
@@ -377,7 +378,8 @@ class UserPrivateAwards(Resource):
|
|||||||
if response.errors:
|
if response.errors:
|
||||||
return {"success": False, "errors": response.errors}, 400
|
return {"success": False, "errors": response.errors}, 400
|
||||||
|
|
||||||
return {"success": True, "data": response.data}
|
count = len(response.data)
|
||||||
|
return {"success": True, "data": response.data, "meta": {"count": count}}
|
||||||
|
|
||||||
|
|
||||||
@users_namespace.route("/<user_id>/solves")
|
@users_namespace.route("/<user_id>/solves")
|
||||||
@@ -399,7 +401,8 @@ class UserPublicSolves(Resource):
|
|||||||
if response.errors:
|
if response.errors:
|
||||||
return {"success": False, "errors": response.errors}, 400
|
return {"success": False, "errors": response.errors}, 400
|
||||||
|
|
||||||
return {"success": True, "data": response.data}
|
count = len(response.data)
|
||||||
|
return {"success": True, "data": response.data, "meta": {"count": count}}
|
||||||
|
|
||||||
|
|
||||||
@users_namespace.route("/<user_id>/fails")
|
@users_namespace.route("/<user_id>/fails")
|
||||||
@@ -423,8 +426,8 @@ class UserPublicFails(Resource):
|
|||||||
data = response.data
|
data = response.data
|
||||||
else:
|
else:
|
||||||
data = []
|
data = []
|
||||||
count = len(response.data)
|
|
||||||
|
|
||||||
|
count = len(response.data)
|
||||||
return {"success": True, "data": data, "meta": {"count": count}}
|
return {"success": True, "data": data, "meta": {"count": count}}
|
||||||
|
|
||||||
|
|
||||||
@@ -446,7 +449,8 @@ class UserPublicAwards(Resource):
|
|||||||
if response.errors:
|
if response.errors:
|
||||||
return {"success": False, "errors": response.errors}, 400
|
return {"success": False, "errors": response.errors}, 400
|
||||||
|
|
||||||
return {"success": True, "data": response.data}
|
count = len(response.data)
|
||||||
|
return {"success": True, "data": response.data, "meta": {"count": count}}
|
||||||
|
|
||||||
|
|
||||||
@users_namespace.route("/<int:user_id>/email")
|
@users_namespace.route("/<int:user_id>/email")
|
||||||
|
|||||||
51
CTFd/constants/assets.py
Normal file
51
CTFd/constants/assets.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from flask import current_app, url_for
|
||||||
|
|
||||||
|
from CTFd.utils import _get_asset_json
|
||||||
|
from CTFd.utils.config import ctf_theme
|
||||||
|
from CTFd.utils.helpers import markup
|
||||||
|
|
||||||
|
|
||||||
|
class _AssetsWrapper:
|
||||||
|
def manifest(self):
|
||||||
|
theme = ctf_theme()
|
||||||
|
manifest = os.path.join(
|
||||||
|
current_app.root_path, "themes", theme, "static", "manifest.json"
|
||||||
|
)
|
||||||
|
return _get_asset_json(path=manifest)
|
||||||
|
|
||||||
|
def manifest_css(self):
|
||||||
|
theme = ctf_theme()
|
||||||
|
manifest = os.path.join(
|
||||||
|
current_app.root_path, "themes", theme, "static", "manifest-css.json"
|
||||||
|
)
|
||||||
|
return _get_asset_json(path=manifest)
|
||||||
|
|
||||||
|
def js(self, asset_key):
|
||||||
|
asset = self.manifest()[asset_key]
|
||||||
|
entry = asset["file"]
|
||||||
|
imports = asset.get("imports", [])
|
||||||
|
html = ""
|
||||||
|
for i in imports:
|
||||||
|
# TODO: Needs a better recursive solution
|
||||||
|
i = self.manifest()[i]["file"]
|
||||||
|
url = url_for("views.themes_beta", path=i)
|
||||||
|
html += f'<script defer type="module" src="{url}"></script>'
|
||||||
|
url = url_for("views.themes_beta", path=entry)
|
||||||
|
html += f'<script defer type="module" src="{url}"></script>'
|
||||||
|
return markup(html)
|
||||||
|
|
||||||
|
def css(self, asset_key):
|
||||||
|
asset = self.manifest_css()[asset_key]
|
||||||
|
entry = asset["file"]
|
||||||
|
url = url_for("views.themes_beta", path=entry)
|
||||||
|
return markup(f'<link rel="stylesheet" href="{url}">')
|
||||||
|
|
||||||
|
def file(self, asset_key):
|
||||||
|
asset = self.manifest()[asset_key]
|
||||||
|
entry = asset["file"]
|
||||||
|
return url_for("views.themes_beta", path=entry)
|
||||||
|
|
||||||
|
|
||||||
|
Assets = _AssetsWrapper()
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import json
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
import cmarkgfm
|
import cmarkgfm
|
||||||
@@ -26,6 +27,12 @@ def get_app_config(key, default=None):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
@cache.memoize()
|
||||||
|
def _get_asset_json(path):
|
||||||
|
with open(path) as f:
|
||||||
|
return json.loads(f.read())
|
||||||
|
|
||||||
|
|
||||||
@cache.memoize()
|
@cache.memoize()
|
||||||
def _get_config(key):
|
def _get_config(key):
|
||||||
config = db.session.execute(
|
config = db.session.execute(
|
||||||
|
|||||||
@@ -29,7 +29,12 @@ def get_errors():
|
|||||||
|
|
||||||
@current_app.url_defaults
|
@current_app.url_defaults
|
||||||
def env_asset_url_default(endpoint, values):
|
def env_asset_url_default(endpoint, values):
|
||||||
"""Create asset URLs dependent on the current env"""
|
"""
|
||||||
|
Create asset URLs dependent on the current env
|
||||||
|
|
||||||
|
In CTFd 4.0 this url_for behavior and the themes_beta
|
||||||
|
route will be removed in favor of an improved theme system
|
||||||
|
"""
|
||||||
if endpoint == "views.themes":
|
if endpoint == "views.themes":
|
||||||
path = values.get("path", "")
|
path = values.get("path", "")
|
||||||
static_asset = path.endswith(".js") or path.endswith(".css")
|
static_asset = path.endswith(".js") or path.endswith(".css")
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ def init_template_filters(app):
|
|||||||
|
|
||||||
def init_template_globals(app):
|
def init_template_globals(app):
|
||||||
from CTFd.constants import JINJA_ENUMS
|
from CTFd.constants import JINJA_ENUMS
|
||||||
|
from CTFd.constants.assets import Assets
|
||||||
from CTFd.constants.config import Configs
|
from CTFd.constants.config import Configs
|
||||||
from CTFd.constants.plugins import Plugins
|
from CTFd.constants.plugins import Plugins
|
||||||
from CTFd.constants.sessions import Session
|
from CTFd.constants.sessions import Session
|
||||||
@@ -101,6 +102,7 @@ def init_template_globals(app):
|
|||||||
app.jinja_env.globals.update(get_current_user_attrs=get_current_user_attrs)
|
app.jinja_env.globals.update(get_current_user_attrs=get_current_user_attrs)
|
||||||
app.jinja_env.globals.update(get_current_team_attrs=get_current_team_attrs)
|
app.jinja_env.globals.update(get_current_team_attrs=get_current_team_attrs)
|
||||||
app.jinja_env.globals.update(get_ip=get_ip)
|
app.jinja_env.globals.update(get_ip=get_ip)
|
||||||
|
app.jinja_env.globals.update(Assets=Assets)
|
||||||
app.jinja_env.globals.update(Configs=Configs)
|
app.jinja_env.globals.update(Configs=Configs)
|
||||||
app.jinja_env.globals.update(Plugins=Plugins)
|
app.jinja_env.globals.update(Plugins=Plugins)
|
||||||
app.jinja_env.globals.update(Session=Session)
|
app.jinja_env.globals.update(Session=Session)
|
||||||
|
|||||||
@@ -484,3 +484,23 @@ def themes(theme, path):
|
|||||||
if os.path.isfile(cand_path):
|
if os.path.isfile(cand_path):
|
||||||
return send_file(cand_path)
|
return send_file(cand_path)
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
|
@views.route("/themes/<theme>/static/<path:path>")
|
||||||
|
def themes_beta(theme, path):
|
||||||
|
"""
|
||||||
|
This is a copy of the above themes route used to avoid
|
||||||
|
the current appending of .dev and .min for theme assets.
|
||||||
|
|
||||||
|
In CTFd 4.0 this url_for behavior and this themes_beta
|
||||||
|
route will be removed.
|
||||||
|
"""
|
||||||
|
for cand_path in (
|
||||||
|
safe_join(app.root_path, "themes", cand_theme, "static", path)
|
||||||
|
# The `theme` value passed in may not be the configured one, e.g. for
|
||||||
|
# admin pages, so we check that first
|
||||||
|
for cand_theme in (theme, *config.ctf_theme_candidates())
|
||||||
|
):
|
||||||
|
if os.path.isfile(cand_path):
|
||||||
|
return send_file(cand_path)
|
||||||
|
abort(404)
|
||||||
|
|||||||
Reference in New Issue
Block a user