Files
Web-NibblePoker/app.py
Herwin Bozet 0e91b5ed96 Implemented tools as applets, Added Docker CCTV page, Fixed small issues
Update app.py, uuid-generator.yml, and 47 more files...
2025-02-20 17:24:05 +01:00

398 lines
13 KiB
Python

import mimetypes
import os
from html import escape
from typing import Optional
from bs4 import BeautifulSoup
from flask import Flask, request, send_from_directory, url_for, Response
from flask import render_template
from minify_html import minify
from werkzeug.exceptions import HTTPException
from website.content import get_articles, get_projects, get_tools, sanitize_input_tags, load_content_items, get_content, \
get_applets
from website.contributors import reload_contributors_data, get_contributors_data
from website.domains import ALLOWED_DOMAINS
from website.l10n.utils import get_user_lang, localize, reload_strings, l10n_url_abs, l10n_url_switch, L10N, \
DEFAULT_LANG
from website.renderers.applet import render_applet_scripts, render_applet_head
from website.renderers.button import render_button
from website.renderers.code import render_code_block
from website.renderers.headings import render_heading, render_h2, render_h1, render_h3, render_h4
from website.renderers.paragraph import render_paragraph
from website.renderers.lists import render_list_ul
from website.renderers.splide import render_splide
from website.sidebar import reload_sidebar_entries, get_sidebar_entries
from website.sitemap import reload_sitemap_entries, get_sitemap_entries
try:
from rich import print
except ImportError:
pass
if os.environ.get('NP_HTML_POST_PROCESS', "NONE") == "MINIFY":
print("Using 'minify' as HTML post-processor")
def post_process_html(html: str) -> str:
return minify(html).replace("> <", "><")
elif os.environ.get('NP_HTML_POST_PROCESS', "NONE") == "BS4":
print("Using 'BeautifulSoup4' as HTML post-processor")
def post_process_html(html: str) -> str:
return BeautifulSoup(html, features="html.parser").prettify()
else:
print("Using no HTML post-processor")
def post_process_html(html: str) -> str:
return html
app = Flask(
import_name=__name__,
static_folder='static',
static_url_path='/',
template_folder='templates',
)
app.jinja_env.trim_blocks = True
app.jinja_env.lstrip_blocks = True
app.jinja_env.strip_trailing_newlines = True
mimetypes.add_type('application/javascript', '.mjs')
@app.after_request
def add_common_headers(response):
# print(response.headers)
response.headers['X-Frame-Options'] = "deny"
# #Header always set Content-Security-Policy "default-src 'self' files.nibblepoker.lu; img-src 'self'
# files.nibblepoker.lu data:; object-src 'none'; child-src 'self'; frame-ancestors 'none';
# upgrade-insecure-requests; block-all-mixed-content"
response.headers['X-XSS-Protection'] = "1; mode=block"
response.headers['Referrer-Policy'] = "no-referrer"
response.headers['X-Content-Type-Options'] = "nosniff"
response.headers['Strict-Transport-Security'] = "max-age=31536000; includeSubDomains; preload"
response.headers['Access-Control-Allow-Origin'] = "*"
response.headers['Permissions-Policy'] = "browsing-topics=(), interest-cohort=()"
if app.debug:
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '0'
else:
response.headers['Cache-Control'] = "max-age=300, public"
return response
# https://flask.palletsprojects.com/en/2.3.x/templating/#context-processors
@app.context_processor
def inject_processors():
return dict(
# Domain
domain_host=request.headers['Host'],
domain_tld=request.headers['Host'].split('.')[-1] if request.headers['Host'] in ALLOWED_DOMAINS else "lu",
domain_url_root=request.url_root,
# L10N
l10n=localize,
l10n_url_abs=l10n_url_abs,
l10n_url_switch=l10n_url_switch,
# Sidebar
get_sidebar_entries=get_sidebar_entries,
# Content
get_content=get_content,
get_articles=get_articles,
get_projects=get_projects,
get_tools=get_tools,
# Renderers
render_button=render_button,
render_heading=render_heading,
render_h1=render_h1,
render_h2=render_h2,
render_h3=render_h3,
render_h4=render_h4,
render_paragraph=render_paragraph,
render_list_ul=render_list_ul,
render_splide=render_splide,
render_applet_scripts=render_applet_scripts,
render_applet_head=render_applet_head,
render_code_block=render_code_block,
# Commons
url_for=url_for,
escape=escape,
)
@app.route('/favicon.svg')
@app.route('/favicon.ico')
def route_favicon():
return send_from_directory(app.static_folder, 'favicon.ico', mimetype='image/vnd.microsoft.icon')
@app.route('/robots.txt')
def route_robots_txt():
return Response(render_template("robots.jinja"), mimetype="")
@app.route('/sitemap.txt')
def route_sitemap():
# FIXME: Add the domain !!!
return Response("\n".join(get_sitemap_entries()), mimetype="")
@app.route('/', defaults={'lang': None})
@app.route('/en/', defaults={'lang': "en"})
@app.route('/fr/', defaults={'lang': "fr"})
def route_root(lang: Optional[str]):
user_lang = get_user_lang(lang, request.headers.get("HTTP_ACCEPT_LANGUAGE"))
return post_process_html(render_template(
"pages/root.jinja",
user_lang=user_lang,
raw_lang=lang,
request_path=request.path,
standalone="standalone" in request.args,
)).replace("> <", "><")
@app.route('/contact/', defaults={'lang': None})
@app.route('/en/contact/', defaults={'lang': "en"})
@app.route('/fr/contact/', defaults={'lang': "fr"})
def route_contact(lang: Optional[str]):
user_lang = get_user_lang(lang, request.headers.get("HTTP_ACCEPT_LANGUAGE"))
return post_process_html(render_template(
"pages/contact.jinja",
user_lang=user_lang,
raw_lang=lang,
request_path=request.path,
standalone="standalone" in request.args,
)).replace("> <", "><")
@app.route('/content/', defaults={'lang': None})
@app.route('/en/content/', defaults={'lang': "en"})
@app.route('/fr/content/', defaults={'lang': "fr"})
def route_content(lang: Optional[str]):
user_lang = get_user_lang(lang, request.headers.get("HTTP_ACCEPT_LANGUAGE"))
try:
requested_tags = sanitize_input_tags(request.args.get("tags", ""))
except ValueError:
requested_tags = None
return post_process_html(render_template(
"pages/project_index.jinja",
user_lang=user_lang,
raw_lang=lang,
request_path=request.path,
standalone="standalone" in request.args,
requested_tags=requested_tags,
)).replace("> <", "><")
@app.route('/content/<project_id>/', defaults={'lang': None})
@app.route('/en/content/<project_id>/', defaults={'lang': "en"})
@app.route('/fr/content/<project_id>/', defaults={'lang': "fr"})
def route_content_project(lang: Optional[str], project_id: str):
user_lang = get_user_lang(lang, request.headers.get("HTTP_ACCEPT_LANGUAGE"))
error_key: Optional[str] = None
error_code: int = 200
if not project_id.replace("-", "").isalnum():
error_key = "content_id_alphanumeric"
error_code = 400
elif project_id not in get_projects().keys():
error_key = "content_id_not_exist"
error_code = 404
if error_key is not None:
return post_process_html(render_template(
"pages/error.jinja",
user_lang=user_lang,
raw_lang=lang,
request_path=request.path,
standalone="standalone" in request.args,
error_key=error_key,
error_code=error_code,
)).replace("> <", "><"), error_code
else:
return post_process_html(render_template(
"projects/" + project_id + ".jinja",
user_lang=user_lang,
raw_lang=lang,
request_path=request.path,
standalone="standalone" in request.args,
project_data=get_projects().get(project_id),
project_id=project_id,
)).replace("> <", "><")
@app.route('/tools/', defaults={'lang': None})
@app.route('/en/tools/', defaults={'lang': "en"})
@app.route('/fr/tools/', defaults={'lang': "fr"})
def route_tools_index(lang: Optional[str]):
user_lang = get_user_lang(lang, request.headers.get("HTTP_ACCEPT_LANGUAGE"))
try:
requested_tags = sanitize_input_tags(request.args.get("tags", ""))
except ValueError:
requested_tags = None
return post_process_html(render_template(
"pages/tools_index.jinja",
user_lang=user_lang,
raw_lang=lang,
request_path=request.path,
standalone="standalone" in request.args,
requested_tags=requested_tags,
)).replace("> <", "><")
@app.route('/tools/<tool_id>/', defaults={'lang': None})
@app.route('/en/tools/<tool_id>/', defaults={'lang': "en"})
@app.route('/fr/tools/<tool_id>/', defaults={'lang': "fr"})
def route_tools_page(lang: Optional[str], tool_id: str):
user_lang = get_user_lang(lang, request.headers.get("HTTP_ACCEPT_LANGUAGE"))
error_key: Optional[str] = None
error_code: int = 200
if not tool_id.replace("-", "").isalnum():
error_key = "content_id_alphanumeric"
error_code = 400
elif tool_id not in get_tools().keys():
error_key = "content_id_not_exist"
error_code = 404
if error_key is not None:
return post_process_html(render_template(
"pages/error.jinja",
user_lang=user_lang,
raw_lang=lang,
request_path=request.path,
standalone="standalone" in request.args,
error_key=error_key,
error_code=error_code,
)).replace("> <", "><"), error_code
else:
return post_process_html(render_template(
"pages/tools_page.jinja",
user_lang=user_lang,
raw_lang=lang,
request_path=request.path,
standalone="standalone" in request.args,
tool_data=get_tools().get(tool_id),
tool_id=tool_id,
applet_data=get_applets().get(get_tools().get(tool_id).applet_id),
)).replace("> <", "><")
@app.route('/about/', defaults={'lang': None})
@app.route('/en/about/', defaults={'lang': "en"})
@app.route('/fr/about/', defaults={'lang': "fr"})
def route_about(lang: Optional[str]):
user_lang = get_user_lang(lang, request.headers.get("HTTP_ACCEPT_LANGUAGE"))
return post_process_html(render_template(
"pages/about.jinja",
user_lang=user_lang,
raw_lang=lang,
request_path=request.path,
standalone="standalone" in request.args,
)).replace("> <", "><")
@app.route('/privacy/', defaults={'lang': None})
@app.route('/en/privacy/', defaults={'lang': "en"})
@app.route('/fr/privacy/', defaults={'lang': "fr"})
def route_privacy(lang: Optional[str]):
user_lang = get_user_lang(lang, request.headers.get("HTTP_ACCEPT_LANGUAGE"))
return post_process_html(render_template(
"pages/privacy.jinja",
user_lang=user_lang,
raw_lang=lang,
request_path=request.path,
standalone="standalone" in request.args,
)).replace("> <", "><")
@app.route('/links/', defaults={'lang': None})
@app.route('/en/links/', defaults={'lang': "en"})
@app.route('/fr/links/', defaults={'lang': "fr"})
def route_links(lang: Optional[str]):
user_lang = get_user_lang(lang, request.headers.get("HTTP_ACCEPT_LANGUAGE"))
return post_process_html(render_template(
"pages/links.jinja",
user_lang=user_lang,
raw_lang=lang,
request_path=request.path,
standalone="standalone" in request.args,
)).replace("> <", "><")
@app.route('/debug/', defaults={'lang': None})
@app.route('/en/debug/', defaults={'lang': "en"})
@app.route('/fr/debug/', defaults={'lang': "fr"})
def route_debug(lang: Optional[str]):
user_lang = get_user_lang(lang, request.headers.get("HTTP_ACCEPT_LANGUAGE"))
return post_process_html(render_template(
"pages/debug.jinja",
user_lang=user_lang,
raw_lang=lang,
request_path=request.path,
standalone="standalone" in request.args,
)).replace("> <", "><")
@app.errorhandler(Exception)
def handle_exception(e: Exception):
# FIXME: Use the user's lang !
# user_lang = get_user_lang(lang, request.headers.get("HTTP_ACCEPT_LANGUAGE"))
error_code = 500
if isinstance(e, HTTPException):
error_code = e.code
return post_process_html(render_template(
"pages/error.jinja",
user_lang=DEFAULT_LANG,
raw_lang=DEFAULT_LANG,
request_path=request.path,
standalone="standalone" in request.args,
error_key=str(e.code),
error_code=e.code,
)).replace("> <", "><"), error_code
if __name__ == '__main__':
load_content_items()
reload_strings(os.path.join(os.getcwd(), "data/strings/"))
reload_sidebar_entries(os.path.join(os.getcwd(), "data/sidebar.yml"))
reload_contributors_data(os.path.join(os.getcwd(), "data/contributors.yml"))
reload_sitemap_entries(os.path.join(os.getcwd(), "data/sitemap.yml"))
# try:
# os.remove("data/strings/dumps.json")
# except OSError:
# pass
#
# try:
# with open("data/strings/dumps.json", "w") as f:
# f.write(json.dumps(L10N._langs_data, indent=2))
# except Exception as err:
# print(err)
# from waitress import serve
# serve(app, host='0.0.0.0', port=5000, threads=64)
app.run(
host="0.0.0.0",
port=5000,
debug=True,
#debug=False,
load_dotenv=False
)