diff --git a/.github/deploy.sh b/.github/deploy.sh index 7b6757a..82487f7 100755 --- a/.github/deploy.sh +++ b/.github/deploy.sh @@ -12,6 +12,9 @@ git reset --hard origin/$BRANCH # Optionally run build or install commands here if needed # e.g., npm install, yarn install for Node.js projects +npm install +npx vite build + /home/loki/.local/bin/uv sync || exit /home/loki/.local/bin/uv run flask db upgrade || exit diff --git a/Makefile b/Makefile index 898014b..89e7300 100644 --- a/Makefile +++ b/Makefile @@ -28,8 +28,13 @@ test-coverage: check: lint test +css-build: + npm install + npx vite build + run: uv run flask db upgrade + npx vite build # Build CSS before running the app uv run flask run --host="0.0.0.0" --debug docker-build: diff --git a/blog/post/views.py b/blog/post/views.py index a0603a2..268b493 100644 --- a/blog/post/views.py +++ b/blog/post/views.py @@ -1,8 +1,6 @@ import datetime from typing import cast, ParamSpec, TypeVar -from functools import wraps -from collections.abc import Callable import markdown from flask import ( Blueprint, @@ -13,6 +11,7 @@ render_template, request, url_for, + abort, ) @@ -26,48 +25,47 @@ R = TypeVar("R") -def pages_gen( - f: Callable[P, R], -) -> Callable[P, R]: - @wraps(f) - def decorated_function(*args: P.args, **kwargs: P.kwargs) -> R: - page_category = cast("list[int]", current_app.config["PAGE_CATEGORY"]) - - post_service = ServiceFactory.create_post_service() - pages = post_service.get_page_posts(page_category) +@post.route("/") +@cache.cached(timeout=50) # pyright: ignore[reportUntypedFunctionDecorator] +def index(**kwargs: str) -> Response | str: + return render_template("index.html", **kwargs) - icon_service = ServiceFactory.create_icon_service() - icons = icon_service.get_all_icons() - kwargs["icons"] = icons - kwargs["pages"] = pages - return f(*args, **kwargs) - return decorated_function +@post.route("/posts") +@cache.cached(timeout=50) # pyright: ignore[reportUntypedFunctionDecorator] +def posts(**kwargs: str) -> Response | str: + post_service = ServiceFactory.create_post_service() + posts = post_service.get_all_published_content() + return render_template("posts.html", posts=posts, **kwargs) -@post.route("/") +@post.route("/hx/pages") @cache.cached(timeout=50) # pyright: ignore[reportUntypedFunctionDecorator] -@pages_gen -def index(**kwargs: str) -> Response | str: +def pages_hx() -> Response | str: + page_category = cast("list[int]", current_app.config["PAGE_CATEGORY"]) post_service = ServiceFactory.create_post_service() - posts = post_service.get_published_posts() - return render_template("posts.html", posts=posts, **kwargs) + pages = post_service.get_page_posts(page_category) + return render_template("pages.htmx", pages=pages) + + +@post.route("/hx/icons") +@cache.cached(timeout=50) # pyright: ignore[reportUntypedFunctionDecorator] +def icons_hx() -> Response | str: + icon_service = ServiceFactory.create_icon_service() + icons = icon_service.get_all_icons() + return render_template("icons/icons.htmx", icons=icons) @post.route("/") @cache.cached(timeout=50) # pyright: ignore[reportUntypedFunctionDecorator] -@pages_gen def view(alias: str | None = None, **kwargs: str) -> Response | str: + template = "post.htmx" if request.headers.get("HX-Request") else "post.html" if alias is None: - from flask import abort - abort(404) post_service = ServiceFactory.create_post_service() post = post_service.get_post_by_alias(alias) if not post: - from flask import abort - abort(404) # For page categories, we need to check if it's a page or a regular post @@ -76,8 +74,6 @@ def view(alias: str | None = None, **kwargs: str) -> Response | str: is_published = post.publishedon is not None if not (is_published or is_page): - from flask import abort - abort(404) # Get tags for the post @@ -89,8 +85,8 @@ def view(alias: str | None = None, **kwargs: str) -> Response | str: page_category_obj = category_service.get_category_by_id(post.category_id) if page_category_obj and page_category_obj.template: - return render_template("post.html", post=post, tags=tags, **kwargs) - return render_template("post.html", post=post, tags=tags, **kwargs) + return render_template(template, post=post, tags=tags, **kwargs) + return render_template(template, post=post, tags=tags, **kwargs) @flask_sitemap.register_generator diff --git a/blog/repos/post.py b/blog/repos/post.py index 4f860f2..db2f0df 100644 --- a/blog/repos/post.py +++ b/blog/repos/post.py @@ -90,6 +90,17 @@ def get_published_posts(self) -> list[PostDomain]: posts_orm = list(self.session.scalars(stmt).all()) return [self._to_domain_model(post_orm) for post_orm in posts_orm] + def get_all_published_content(self) -> list[PostDomain]: + """Get all published content including posts and pages.""" + stmt = ( + sa.select(PostORM) + .where(PostORM.publishedon.isnot(None)) + .where(PostORM.category_id.isnot(1)) + .order_by(PostORM.publishedon.desc()) + ) + posts_orm = list(self.session.scalars(stmt).all()) + return [self._to_domain_model(post_orm) for post_orm in posts_orm] + def get_page_posts(self, page_category_ids: list[int]) -> list[PostDomain]: stmt = sa.select(PostORM).where(PostORM.category_id.in_(page_category_ids)) posts_orm = list(self.session.scalars(stmt).all()) diff --git a/blog/services/post.py b/blog/services/post.py index 6e84ca7..85fec2b 100644 --- a/blog/services/post.py +++ b/blog/services/post.py @@ -49,6 +49,9 @@ def get_all_posts(self) -> list[Post]: def get_published_posts(self) -> list[Post]: return self.post_repository.get_published_posts() + def get_all_published_content(self) -> list[Post]: + return self.post_repository.get_all_published_content() + def get_page_posts(self, page_category_ids: list[int]) -> list[Post]: return self.post_repository.get_page_posts(page_category_ids) diff --git a/blog/static/src/global/fonts.css b/blog/static/src/global/fonts.css index 315b220..34effb2 100644 --- a/blog/static/src/global/fonts.css +++ b/blog/static/src/global/fonts.css @@ -2,12 +2,12 @@ font-family: 'Open Sans'; font-style: normal; font-weight: 400; - src: local('Open Sans'), local('OpenSans'), url(../../fonts/OpenSans.ttf) format('truetype'); + src: local('Open Sans'), local('OpenSans'), url(/static/fonts/OpenSans.ttf) format('truetype'); } @font-face { font-family: 'Open Sans'; font-style: normal; font-weight: 700; - src: local('Open Sans Bold'), local('OpenSans-Bold'), url(../../fonts/OpenSans-Bold.ttf) format('truetype'); -} \ No newline at end of file + src: local('Open Sans Bold'), local('OpenSans-Bold'), url(/static/fonts/OpenSans-Bold.ttf) format('truetype'); +} diff --git a/blog/static/src/styles.css b/blog/static/src/styles.css index 1baa91c..a445cdd 100644 --- a/blog/static/src/styles.css +++ b/blog/static/src/styles.css @@ -11,6 +11,4 @@ @import 'components/page.css'; @import 'components/post.css'; @import 'components/nav.css'; -@import 'components/works.css'; -@import 'components/work.css'; @import 'components/refs.css'; diff --git a/blog/tags/views.py b/blog/tags/views.py index 05e37ab..797aec6 100644 --- a/blog/tags/views.py +++ b/blog/tags/views.py @@ -1,8 +1,7 @@ -from typing import TYPE_CHECKING, ParamSpecKwargs +from typing import TYPE_CHECKING -from flask import Blueprint, render_template, Response +from flask import Blueprint, render_template, Response, abort, request -from blog.post.views import pages_gen from blog.services.factory import ServiceFactory if TYPE_CHECKING: @@ -12,29 +11,25 @@ @tags.route("/") -@pages_gen -def index(**kwargs: ParamSpecKwargs) -> Response | str: +def index() -> Response | str: + template = "tags.htmx" if request.headers.get("HX-Request") else "tags.html" tag_service = ServiceFactory.create_tag_service() tags = tag_service.get_all_tags() - return render_template("tags.html", tags=tags, **kwargs) + return render_template(template, tags=tags) @tags.route("/") -@pages_gen -def view(alias: str | None = None, **kwargs: ParamSpecKwargs) -> Response | str: +def view(alias: str | None = None) -> Response | str: + template = "posts.htmx" if request.headers.get("HX-Request") else "tag.html" if alias is None: - from flask import abort - abort(404) tag_service = ServiceFactory.create_tag_service() tag = tag_service.get_tag_by_alias(alias) if not tag: - from flask import abort - abort(404) # Get posts for this tag through the post service post_service = ServiceFactory.create_post_service() posts = post_service.get_posts_by_tag(tag.id) if tag.id else [] - return render_template("posts.html", posts=posts, tag=tag, **kwargs) + return render_template(template, posts=posts, tag=tag) diff --git a/blog/templates/footer.html b/blog/templates/footer.html index 3d12c40..1f22867 100644 --- a/blog/templates/footer.html +++ b/blog/templates/footer.html @@ -1,6 +1,11 @@