Skip to content

Commit 48b7634

Browse files
committed
Render changelog
1 parent a7ff235 commit 48b7634

File tree

9 files changed

+202
-2
lines changed

9 files changed

+202
-2
lines changed

futuramaapi/helpers/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from ._markdown import render_markdown
2+
3+
__all__ = [
4+
"render_markdown",
5+
]

futuramaapi/helpers/_markdown.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import bleach
2+
import markdown
3+
4+
ALLOWED_TAGS = bleach.sanitizer.ALLOWED_TAGS | {
5+
"h1",
6+
"h2",
7+
"h3",
8+
"h4",
9+
"p",
10+
"pre",
11+
"code",
12+
"ul",
13+
"ol",
14+
"li",
15+
"strong",
16+
"em",
17+
"blockquote",
18+
"hr",
19+
"a",
20+
}
21+
22+
ALLOWED_ATTRIBUTES = {
23+
"a": [
24+
"href",
25+
"title",
26+
],
27+
}
28+
29+
ALLOWED_PROTOCOLS = {
30+
"http",
31+
"https",
32+
"mailto",
33+
}
34+
35+
36+
def render_markdown(md: str, /) -> str:
37+
html = markdown.markdown(
38+
md,
39+
extensions=[
40+
"extra",
41+
"fenced_code",
42+
"toc",
43+
],
44+
output_format="html",
45+
)
46+
47+
return bleach.clean(
48+
html,
49+
tags=ALLOWED_TAGS,
50+
attributes=ALLOWED_ATTRIBUTES,
51+
protocols=ALLOWED_PROTOCOLS,
52+
strip=True,
53+
)

futuramaapi/routers/rest/root/api.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from futuramaapi.routers.rest.users.dependencies import cookie_user_from_form_data, user_from_cookies
1212
from futuramaapi.routers.rest.users.schemas import Link, User
1313

14-
from .schemas import About, Root, SiteMap, UserAuth
14+
from .schemas import About, Changelog, Root, SiteMap, UserAuth
1515

1616
router = APIRouter()
1717

@@ -189,3 +189,16 @@ async def get_sitemap(
189189
) -> Response:
190190
obj: SiteMap = await SiteMap.from_request(request)
191191
return await obj.get_response()
192+
193+
194+
@router.get(
195+
"/changelog",
196+
include_in_schema=False,
197+
name="changelog",
198+
)
199+
async def get_changelog(
200+
request: Request,
201+
session: AsyncSession = Depends(get_async_session), # noqa: B008
202+
):
203+
obj: Changelog = await Changelog.from_request(session, request)
204+
return await obj.get_response(request)

futuramaapi/routers/rest/root/schemas.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
from datetime import UTC, datetime, timedelta
2+
from pathlib import Path
23
from typing import TYPE_CHECKING, ClassVar, Self
34

5+
import aiofiles
6+
from aiocache import Cache, cached
47
from fastapi import Response
58
from pydantic import HttpUrl
69
from sqlalchemy import Select, select
710
from sqlalchemy.ext.asyncio import AsyncSession
811
from starlette.requests import Request
912

1013
from futuramaapi.core import settings
14+
from futuramaapi.helpers import render_markdown
1115
from futuramaapi.helpers.pydantic import BaseModel, Field
1216
from futuramaapi.mixins.pydantic import BaseModelTemplateMixin, ProjectContext
1317
from futuramaapi.repositories import FilterStatementKwargs
@@ -106,3 +110,26 @@ async def from_request(cls, request: Request) -> Self:
106110
return cls(
107111
urls=[url.path for url in app.public_urls],
108112
)
113+
114+
115+
@cached(
116+
ttl=None,
117+
cache=Cache.MEMORY,
118+
)
119+
async def _get_rendered_content() -> str:
120+
path: Path = Path(settings.project_root) / "CHANGELOG.md"
121+
async with aiofiles.open(path, encoding="utf-8") as f:
122+
raw_content = await f.read()
123+
return render_markdown(raw_content)
124+
125+
126+
class Changelog(BaseModel, BaseModelTemplateMixin):
127+
content: str
128+
129+
template_name: ClassVar[str] = "changelog.html"
130+
131+
@classmethod
132+
async def from_request(cls, session: AsyncSession, request: Request, /) -> Self:
133+
return cls(
134+
content=await _get_rendered_content(),
135+
)

poetry.lock

Lines changed: 59 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ redis = "^7.1.0"
4141
pydantic = "^2.12.5"
4242
alembic = "^1.17.2"
4343
strawberry-graphql = "^0.288.2"
44+
markdown = "^3.10"
45+
bleach = "^6.3.0"
46+
aiofiles = "^25.1.0"
4447

4548
[tool.poetry.group.dev.dependencies]
4649
pre-commit = "^4.5.1"

static/css/changelog.css

Whitespace-only changes.

templates/base.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,17 @@ <h3>Hi, {{ current_user.full_name }}, welcome to</h3>
116116
<div
117117
class="col-lg-6 mx-auto"
118118
>
119+
<div
120+
class="d-flex justify-content-center"
121+
>
122+
<a
123+
href="{{ relative_path_for('changelog') }}"
124+
class="btn btn-info"
125+
role="button"
126+
target="_blank"
127+
>Changelog
128+
</a>
129+
</div>
119130
<div
120131
class="d-grid d-sm-flex justify-content-sm-center mb-5"
121132
>

templates/changelog.html

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{% extends "base.html" %}
2+
3+
{% set active_page = "changelog" %}
4+
5+
{% block title %}Changelog{% endblock %} | Futurama API
6+
7+
{% block extra_styles %}
8+
<link
9+
href="{{ relative_path_for('static', path='/css/changelog.css') }}"
10+
rel="stylesheet"
11+
/>
12+
{% endblock %}
13+
14+
{% block main_info %}{% endblock %}
15+
16+
{% block main_content %}
17+
<div
18+
class="container my-5"
19+
>
20+
<div
21+
class="row justify-content-center"
22+
>
23+
<div
24+
class="col-lg-9 col-xl-8 changelog"
25+
>
26+
{{ content | safe }}
27+
</div>
28+
</div>
29+
</div>
30+
{% endblock %}

0 commit comments

Comments
 (0)