Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Deployment/application.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
{
"flask_secret_key": "secret",
"metrics_token": "metrics"
}
29 changes: 17 additions & 12 deletions Sources/website/benjaminhamon_developer_website/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,14 @@ def run(self, address: Optional[str] = None, port: Optional[int] = None, debug:
self._flask_application.run(host = address, port = port, debug = debug)


def log_request(self) -> None:
request_logger.info("(%s) %s %s", flask.request.environ["REMOTE_ADDR"], flask.request.method, flask.request.base_url)
def check_request(self) -> None:
requested_locale = self._get_requested_locale()
if requested_locale is not None and requested_locale not in self._flask_application.config["LOCALE_SUPPORTED"]:
raise werkzeug.exceptions.NotFound


def refresh_session(self) -> None:
flask.session.permanent = True

if "locale" in flask.request.args:
flask.session["locale"] = flask.request.args["locale"]
if "locale" not in flask.session:
flask.session["locale"] = "en"
if flask.session["locale"] not in [ "en", "fr" ]:
flask.session["locale"] = "en"
def log_request(self) -> None:
request_logger.info("(%s) %s %s", flask.request.environ["REMOTE_ADDR"], flask.request.method, flask.request.base_url)


def handle_error(self, exception: Any) -> flask.typing.ResponseReturnValue:
Expand All @@ -54,4 +49,14 @@ def handle_error(self, exception: Any) -> flask.typing.ResponseReturnValue:
if flask.request.url_rule is not None and flask.request.url_rule.rule == "/metrics":
return "", status_code, { "Content-Type": "text/plain" }

return flask.render_template(flask.session["locale"] + "/" + "error.html", message = status_message, status_code = status_code), status_code
requested_locale = self._get_requested_locale()
if requested_locale is None or requested_locale not in self._flask_application.config["LOCALE_SUPPORTED"]:
requested_locale = self._flask_application.config["LOCALE_DEFAULT"]

return flask.render_template(requested_locale + "/" + "error.html", message = status_message, status_code = status_code), status_code


def _get_requested_locale(self) -> Optional[str]:
if flask.request.url_rule is not None and flask.request.view_args is not None:
return flask.request.view_args.get("locale", None)
return flask.request.path.split("/")[1]
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# cspell:words werkzeug

import datetime
import functools
import logging
from typing import Callable, List
Expand All @@ -19,41 +18,36 @@
request_logger = logging.getLogger("Request")


def create_application(flask_secret_key: str, metrics_token: str) -> Application:
title = "Benjamin Hamon's developer website"
sources_url = "https://github.com/BenjaminHamon/DeveloperWebsite"
contact_email = "development@benjaminhamon.com"

def create_application(metrics_token: str) -> Application:
flask_application = flask.Flask("benjaminhamon_developer_website")
flask_application.secret_key = flask_secret_key
flask_application.config.update(
SECRET_KEY = flask_secret_key,
SESSION_COOKIE_HTTPONLY = True,
SESSION_COOKIE_SAMESITE = "Lax",
SESSION_COOKIE_SECURE = True,
PERMANENT_SESSION_LIFETIME = datetime.timedelta(days = 7),
METRICS_TOKEN = metrics_token,
)

prometheus_metrics = prometheus_flask_exporter.PrometheusMetrics(None, metrics_decorator = metrics_authorization)
application = Application(flask_application)
main_controller = MainController()

configure(flask_application, title, sources_url, contact_email)
configure(flask_application)
register_handlers(flask_application, application)
register_routes(flask_application, main_controller)
prometheus_metrics.init_app(flask_application)

return application


def configure(application: flask.Flask, title: str, sources_url: str, contact_email: str) -> None:
application.config["WEBSITE_TITLE"] = title
application.config["WEBSITE_COPYRIGHT"] = benjaminhamon_developer_website.__copyright__
application.config["WEBSITE_VERSION"] = benjaminhamon_developer_website.__version__
application.config["WEBSITE_DATE"] = benjaminhamon_developer_website.__date__
application.config["WEBSITE_SOURCES_URL"] = sources_url
application.config["WEBSITE_CONTACT_EMAIL"] = contact_email
def configure(application: flask.Flask) -> None:
application.config["METADATA"] = {
"product": benjaminhamon_developer_website.__product__,
"copyright": benjaminhamon_developer_website.__copyright__,
"version": benjaminhamon_developer_website.__version__,
"date": benjaminhamon_developer_website.__date__,
"sources_url": "https://github.com/BenjaminHamon/DeveloperWebsite",
"contact_email": "development@benjaminhamon.com",
}

application.config["LOCALE_DEFAULT"] = "en"
application.config["LOCALE_SUPPORTED"] = [ "en", "fr" ]

application.jinja_env.undefined = jinja2.StrictUndefined
application.jinja_env.trim_blocks = True
Expand All @@ -65,18 +59,19 @@ def configure(application: flask.Flask, title: str, sources_url: str, contact_em
def register_handlers(flask_application: flask.Flask, application: Application) -> None:
flask_application.log_exception = lambda exc_info: None
flask_application.before_request(application.log_request)
flask_application.before_request(application.refresh_session)
flask_application.before_request(application.check_request)
for exception in werkzeug.exceptions.default_exceptions.values():
flask_application.register_error_handler(exception, application.handle_error)


def register_routes(application: flask.Flask, main_controller: MainController) -> None:
add_url_rule(application, "/", [ "GET" ], main_controller.home)
add_url_rule(application, "/contact", [ "GET" ], main_controller.contact)
add_url_rule(application, "/education", [ "GET" ], main_controller.education)
add_url_rule(application, "/projects", [ "GET" ], main_controller.projects)
add_url_rule(application, "/skills", [ "GET" ], main_controller.skills)
add_url_rule(application, "/work_experience", [ "GET" ], main_controller.work_experience)
add_url_rule(application, "/", [ "GET" ], main_controller.home_default)
add_url_rule(application, "/<locale>", [ "GET" ], main_controller.home)
add_url_rule(application, "/<locale>/contact", [ "GET" ], main_controller.contact)
add_url_rule(application, "/<locale>/education", [ "GET" ], main_controller.education)
add_url_rule(application, "/<locale>/projects", [ "GET" ], main_controller.projects)
add_url_rule(application, "/<locale>/skills", [ "GET" ], main_controller.skills)
add_url_rule(application, "/<locale>/work_experience", [ "GET" ], main_controller.work_experience)


def add_url_rule(application: flask.Flask, path: str, methods: List[str], handler: Callable, **kwargs) -> None:
Expand All @@ -86,7 +81,7 @@ def add_url_rule(application: flask.Flask, path: str, methods: List[str], handle

def versioned_url_for(endpoint: str, **values) -> str:
if endpoint == "static":
values["version"] = flask.current_app.config["WEBSITE_VERSION"]
values["version"] = flask.current_app.config["METADATA"]["version"]
return flask.url_for(endpoint, **values)


Expand Down
29 changes: 17 additions & 12 deletions Sources/website/benjaminhamon_developer_website/main_controller.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
import flask
import werkzeug


class MainController:


def home(self) -> str:
return flask.render_template(flask.session["locale"] + "/" + "home.html")
def home_default(self) -> werkzeug.Response:
return flask.redirect(flask.url_for("main_controller.home", locale = flask.current_app.config["LOCALE_DEFAULT"]))


def education(self) -> str:
return flask.render_template(flask.session["locale"] + "/" + "education.html")
def home(self, locale: str) -> str:
return flask.render_template(locale + "/" + "home.html")


def skills(self) -> str:
return flask.render_template(flask.session["locale"] + "/" + "skills.html")
def education(self, locale: str) -> str:
return flask.render_template(locale + "/" + "education.html")


def projects(self) -> str:
return flask.render_template(flask.session["locale"] + "/" + "projects.html")
def skills(self, locale: str) -> str:
return flask.render_template(locale + "/" + "skills.html")


def work_experience(self) -> str:
return flask.render_template(flask.session["locale"] + "/" + "work_experience.html")
def projects(self, locale: str) -> str:
return flask.render_template(locale + "/" + "projects.html")


def contact(self) -> str:
return flask.render_template(flask.session["locale"] + "/" + "contact.html")
def work_experience(self, locale: str) -> str:
return flask.render_template(locale + "/" + "work_experience.html")


def contact(self, locale: str) -> str:
return flask.render_template(locale + "/" + "contact.html")
2 changes: 1 addition & 1 deletion Sources/website/benjaminhamon_developer_website/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def main():

configure_logging(arguments)

application = application_factory.create_application("secret", "metrics")
application = application_factory.create_application("metrics")
website_url = "http://%s:%s/" % (arguments.address, arguments.port)
os.environ["DEBUG_METRICS"] = "1" # For Prometheus exporter

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<a href="mailto:work@benjaminhamon.com">work@benjaminhamon.com</a>
</div>
<div class="text">
<p>Please use this address for work-related inquiries. See my status on the <a href="{{ url_for('main_controller.home') }}">Home</a> page.</p>
<p>Please use this address for work-related inquiries. See my status on the <a href="{{ url_for('main_controller.home', locale = 'en') }}">Home</a> page.</p>
</div>
</article>
<article class="contact-entry">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<p>Hello! I'm Benjamin.</p>
<p>I'm a 33-year-old French guy with a passion for video games and most computer-related things.</p>
<p>I've worked as a software engineer in the video game industry for about a decade. I typically go by DevOps engineer, and I'm proficient in application development, web development, system administration, and infrastructure management. While I'm not your usual game programmer, you might have glimpsed my name in the credits of such games as Humankind and Life Is Strange 2.</p>
<p>I work on several open source projects, which you can explore in the <a href="{{ url_for('main_controller.projects') }}">Projects</a> page and through <a href="https://github.com/BenjaminHamon/">GitHub</a>.</p>
<p>I work on several open source projects, which you can explore in the <a href="{{ url_for('main_controller.projects', locale = 'en') }}">Projects</a> page and through <a href="https://github.com/BenjaminHamon/">GitHub</a>.</p>
<p>As another matter entirely, I recently became a self-published novelist. If that interests you, you can check out my <a href="https://author.benjaminhamon.com/">author website</a> and my <a href="https://blog-fiction.benjaminhamon.com/">blog about fiction</a>.</p>
</div>
</article>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,40 @@
{# cspell:words stylesheet #}
{# cspell:words français hreflang stylesheet #}

{% import 'macros.html' as shared_macros -%}

<!doctype html>
<html>

<head>
<title>{{ title }} - {{ config['WEBSITE_TITLE'] }}</title>

<title>{{ title }} - Benjamin Hamon's developer website</title>
<meta name="viewport" content="width=device-width"/>
<link rel="stylesheet" href="{{ url_for('static', filename = 'layout.css') }}"/>
<link rel="stylesheet" href="{{ url_for('static', filename = 'generic.css') }}"/>
<link rel="stylesheet" href="{{ url_for('static', filename = 'content.css') }}"/>
<link rel="icon" href="data:,">

{% if request.url_rule %}
<link rel="alternate" hreflang="x-default" href="{{ url_for(request.url_rule.endpoint, _external = True, locale = 'en') }}"/>
<link rel="alternate" hreflang="en" href="{{ url_for(request.url_rule.endpoint, _external = True, locale = 'en') }}"/>
<link rel="alternate" hreflang="fr" href="{{ url_for(request.url_rule.endpoint, _external = True, locale = 'fr') }}"/>
{% endif %}

</head>

<body>

<header>
<div class="title">
<a href="{{ url_for('main_controller.home') }}">{{ config['WEBSITE_TITLE'] }}</a>
<a href="{{ url_for('main_controller.home', locale = 'en') }}">Benjamin Hamon's developer website</a>
</div>
<nav>
{{ shared_macros.navigation_link('main_controller.home', 'Home', [ 'main_controller.home' ]) }}
{{ shared_macros.navigation_link('main_controller.education', 'Education', [ 'main_controller.education' ]) }}
{{ shared_macros.navigation_link('main_controller.work_experience', 'Work Experience', [ 'main_controller.work_experience' ]) }}
{{ shared_macros.navigation_link('main_controller.projects', 'Projects', [ 'main_controller.projects' ]) }}
{{ shared_macros.navigation_link('main_controller.skills', 'Skills', [ 'main_controller.skills' ]) }}
{{ shared_macros.navigation_link('main_controller.contact', 'Contact', [ 'main_controller.contact' ]) }}
{{ shared_macros.navigation_link('main_controller.home', 'en', 'Home', [ 'main_controller.home' ]) }}
{{ shared_macros.navigation_link('main_controller.education', 'en', 'Education', [ 'main_controller.education' ]) }}
{{ shared_macros.navigation_link('main_controller.work_experience', 'en', 'Work Experience', [ 'main_controller.work_experience' ]) }}
{{ shared_macros.navigation_link('main_controller.projects', 'en', 'Projects', [ 'main_controller.projects' ]) }}
{{ shared_macros.navigation_link('main_controller.skills', 'en', 'Skills', [ 'main_controller.skills' ]) }}
{{ shared_macros.navigation_link('main_controller.contact', 'en', 'Contact', [ 'main_controller.contact' ]) }}
</nav>
</header>

Expand All @@ -38,21 +46,21 @@

<footer>
<div>
{{ shared_macros.locale_link('en', '🇬🇧', 'English') }}
{{ shared_macros.locale_link('fr', '🇫🇷', 'Français') }}
{{ shared_macros.locale_link('en', 'en', '🇬🇧', 'English') }}
{{ shared_macros.locale_link('en', 'fr', '🇫🇷', 'Français') }}
</div>
<div>
<p>{{ config['WEBSITE_TITLE'] }}</p>
<p>Benjamin Hamon's developer website</p>
</div>
<div>
<a href="https://www.benjaminhamon.com">Portal</a>
<a href="{{ config['WEBSITE_SOURCES_URL'] }}">Sources</a>
<a href="mailto:{{ config['WEBSITE_CONTACT_EMAIL'] }}">Contact</a>
<a href="{{ config['METADATA']['sources_url'] }}">Sources</a>
<a href="mailto:{{ config['METADATA']['contact_email'] }}">Contact</a>
</div>
<div>
{% if config['WEBSITE_VERSION'] %}
<p>Version {{ config['WEBSITE_VERSION'] }} ({{ config['WEBSITE_DATE'] }})</p>
<p>{{ config['WEBSITE_COPYRIGHT'].replace("(c)", "©") }}</p>
{% if config['METADATA']['version'] %}
<p>Version {{ config['METADATA']['version'] }} ({{ config['METADATA']['date'] }})</p>
<p>{{ config['METADATA']['copyright'].replace("(c)", "©") }}</p>
{% else %}
<p>Development Version</p>
{% endif %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<a href="mailto:work@benjaminhamon.com">work@benjaminhamon.com</a>
</div>
<div class="text">
<p>Merci d'utiliser cette adresse pour les demandes professionnelles. Mon statut est visible sur la page <a href="{{ url_for('main_controller.home') }}">Accueil</a>.</p>
<p>Merci d'utiliser cette adresse pour les demandes professionnelles. Mon statut est visible sur la page <a href="{{ url_for('main_controller.home', locale = 'fr') }}">Accueil</a>.</p>
</div>
</article>
<article class="contact-entry">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<p>Bonjour ! Je suis Benjamin.</p>
<p>J'ai 33 ans, je suis français et j'ai une passion pour les jeux vidéo et l'informatique en général.</p>
<p>J'ai travaillé en tant qu'ingénieur logiciel dans l'industrie du jeu vidéo pendant dix ans. Je me présente généralement comme ingénieur DevOps, et je suis compétent dans les domaines du développement d'application, du développement web, de l'administration des systèmes, et de la gestion d'infrastructure. Je ne suis pas exactement un programmeur de jeux vidéo, mais vous avez peut-être aperçu mon nom dans les crédits de jeux tels que Humankind et Life Is Strange 2.</p>
<p>Je travaille sur plusieurs projets open source, que vous pouvez découvrir au travers de la page <a href="{{ url_for('main_controller.projects') }}">Projets</a> et de mon <a href="https://github.com/BenjaminHamon/">GitHub</a>.</p>
<p>Je travaille sur plusieurs projets open source, que vous pouvez découvrir au travers de la page <a href="{{ url_for('main_controller.projects', locale = 'fr') }}">Projets</a> et de mon <a href="https://github.com/BenjaminHamon/">GitHub</a>.</p>
<p>Dans un tout autre registre, je suis récemment devenu romancier auto-publié. Si cela vous intéresse, vous pouvez jeter un œil à mon <a href="https://author.benjaminhamon.com/">site auteur</a> ainsi qu'à mon <a href="https://blog-fiction.benjaminhamon.com/">blog sur la fiction</a>.</p>
</div>
</article>
Expand Down
Loading