feat: add seo diagnostics tool with cli commands, config paths, and static versioning

Add SEO_REPORTS_DIR to config, STATIC_VERSION for cache-busting, seo prune/clear CLI subcommands, tools router with seo job endpoints, and boot-versioned static URLs in Dockerfile and Makefile
This commit is contained in:
2026-06-14 00:16:22 +00:00
parent 61c1ae8c5d
commit 1b89f7c49f
110 changed files with 4501 additions and 270 deletions
+29 -19
View File
@@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#0f0a1a">
<meta name="asset-version" content="{{ static_version }}">
<title>{% if page_title %}{{ page_title }}{% else %}DevPlace - The Developer Social Network{% endif %}</title>
<meta name="description" content="{% if meta_description %}{{ meta_description }}{% else %}Track industry shifts. Discover bold releases. Share what you're building in an open, uncensored environment.{% endif %}">
<link rel="canonical" href="{{ canonical_url or request.url }}">
@@ -15,33 +16,33 @@
<meta property="og:description" content="{{ og_description or meta_description or 'The Developer Social Network' }}">
<meta property="og:type" content="{{ og_type or 'website' }}">
<meta property="og:url" content="{{ canonical_url or request.url }}">
<meta property="og:image" content="{{ og_image or '/static/og-default.png' }}">
<meta property="og:image" content="{{ static_url(og_image or '/static/og-default.png') }}">
<meta property="og:site_name" content="DevPlace">
<meta property="og:locale" content="en_US">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ og_title or page_title or 'DevPlace' }}">
<meta name="twitter:description" content="{{ og_description or meta_description or 'The Developer Social Network' }}">
<meta name="twitter:image" content="{{ og_image or '/static/og-default.png' }}">
<meta name="twitter:image" content="{{ static_url(og_image or '/static/og-default.png') }}">
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>&#x1f4bb;</text></svg>">
<link rel="apple-touch-icon" href="/static/apple-touch-icon.png">
<link rel="apple-touch-icon" href="{{ static_url('/static/apple-touch-icon.png') }}">
<link rel="manifest" href="/manifest.json">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="DevPlace">
<link rel="stylesheet" href="/static/css/variables.css">
<link rel="stylesheet" href="/static/css/base.css">
<link rel="stylesheet" href="/static/css/components.css">
<link rel="stylesheet" href="/static/vendor/atom-one-dark.min.css">
<link rel="stylesheet" href="/static/css/markdown.css">
<link rel="stylesheet" href="/static/css/mention.css">
<link rel="stylesheet" href="/static/css/attachments.css">
<link rel="stylesheet" href="/static/css/lightbox.css">
<link rel="stylesheet" href="/static/css/media.css">
<link rel="stylesheet" href="/static/css/engagement.css">
<link rel="stylesheet" href="{{ static_url('/static/css/variables.css') }}">
<link rel="stylesheet" href="{{ static_url('/static/css/base.css') }}">
<link rel="stylesheet" href="{{ static_url('/static/css/components.css') }}">
<link rel="stylesheet" href="{{ static_url('/static/vendor/atom-one-dark.min.css') }}">
<link rel="stylesheet" href="{{ static_url('/static/css/markdown.css') }}">
<link rel="stylesheet" href="{{ static_url('/static/css/mention.css') }}">
<link rel="stylesheet" href="{{ static_url('/static/css/attachments.css') }}">
<link rel="stylesheet" href="{{ static_url('/static/css/lightbox.css') }}">
<link rel="stylesheet" href="{{ static_url('/static/css/media.css') }}">
<link rel="stylesheet" href="{{ static_url('/static/css/engagement.css') }}">
{% block extra_head %}{% endblock %}
{{ custom_css_tag(request) }}
@@ -60,8 +61,14 @@
<a href="/feed" class="topnav-link {% if request.url.path == '/feed' %}active{% endif %}"><span class="icon">πŸ“</span> Posts</a>
<a href="/news" class="topnav-link {% if 'news' in request.url.path %}active{% endif %}"><span class="icon">πŸ“°</span> News</a>
<a href="/gists" class="topnav-link {% if 'gists' in request.url.path %}active{% endif %}"><span class="icon">πŸ“„</span> Gists</a>
<a href="/projects" class="topnav-link {% if 'projects' in request.url.path %}active{% endif %}"><span class="icon">πŸš€</span> Projects</a>
<a href="/projects" class="topnav-link topnav-link-feature {% if 'projects' in request.url.path %}active{% endif %}"><span class="icon">πŸš€</span> Projects</a>
<a href="/leaderboard" class="topnav-link {% if 'leaderboard' in request.url.path %}active{% endif %}"><span class="icon">πŸ†</span> Leaderboard</a>
<div class="topnav-tools-dropdown {% if request.url.path.startswith('/tools') %}active{% endif %}">
<button type="button" class="topnav-link topnav-tools-toggle" aria-haspopup="true" aria-expanded="false"><span class="icon">🧰</span> Tools <span class="topnav-caret">β–Ύ</span></button>
<div class="dropdown-menu">
<a href="/tools/seo" class="dropdown-item"><span class="icon">πŸ”</span> SEO Diagnostics</a>
</div>
</div>
{% if user %}
<a href="/messages" class="topnav-link {% if 'messages' in request.url.path %}active{% endif %}" data-counter="messages"><span class="icon">βœ‰οΈ</span> Messages{% set msg_unread = get_unread_messages(user["uid"]) %}<span class="nav-badge nav-badge-inline" data-counter-badge {% if msg_unread == 0 %}hidden{% endif %}>{{ msg_unread }}</span></a>
{% if is_admin(user) %}
@@ -112,6 +119,9 @@
<a href="/gists" class="topnav-mobile-link {% if 'gists' in request.url.path %}active{% endif %}"><span class="icon">πŸ“„</span> Gists</a>
<a href="/projects" class="topnav-mobile-link {% if 'projects' in request.url.path %}active{% endif %}"><span class="icon">πŸš€</span> Projects</a>
<a href="/leaderboard" class="topnav-mobile-link {% if 'leaderboard' in request.url.path %}active{% endif %}"><span class="icon">πŸ†</span> Leaderboard</a>
<div class="topnav-mobile-divider"></div>
<div class="topnav-mobile-section-label">Tools</div>
<a href="/tools/seo" class="topnav-mobile-link {% if 'tools/seo' in request.url.path %}active{% endif %}"><span class="icon">πŸ”</span> SEO Diagnostics</a>
{% if user %}
<a href="/messages" class="topnav-mobile-link {% if 'messages' in request.url.path %}active{% endif %}" data-counter="messages"><span class="icon">βœ‰οΈ</span> Messages<span class="nav-badge nav-badge-inline" data-counter-badge {% if get_unread_messages(user["uid"]) == 0 %}hidden{% endif %}>{{ get_unread_messages(user["uid"]) }}</span></a>
<a href="/notifications" class="topnav-mobile-link {% if 'notifications' in request.url.path %}active{% endif %}" data-counter="notifications"><span class="icon">πŸ””</span> Notifications<span class="nav-badge nav-badge-inline" data-counter-badge {% if get_unread_count(user["uid"]) == 0 %}hidden{% endif %}>{{ get_unread_count(user["uid"]) }}</span></a>
@@ -177,11 +187,11 @@
</template>
{% endif %}
<script defer src="/static/vendor/marked.umd.js"></script>
<script defer src="/static/vendor/highlight.min.js"></script>
<script defer src="/static/vendor/purify.min.js"></script>
<script type="module" src="/static/vendor/emoji-picker-element/index.js"></script>
<script type="module" src="/static/js/Application.js"></script>
<script defer src="{{ static_url('/static/vendor/marked.umd.js') }}"></script>
<script defer src="{{ static_url('/static/vendor/highlight.min.js') }}"></script>
<script defer src="{{ static_url('/static/vendor/purify.min.js') }}"></script>
<script type="module" src="{{ static_url('/static/vendor/emoji-picker-element/index.js') }}"></script>
<script type="module" src="{{ static_url('/static/js/Application.js') }}"></script>
{% block extra_js %}{% endblock %}
{{ custom_js_tag(request) }}
</body>