Integrations

About

About integrations.fun

2025-09-08

My name is Ryan. I have over 20 years experience managing and securing IT infrastructure, and I enjoy solving technical problems. For most of my career, I've benefitted from individuals who post solutions and suggestions online. This is my effort to return the favor. I hope you find something helpful here.

How this site is made

This site is built with Flask and hosted on Vercel. I wanted a simple way to make posts using markdown files. I drop a .md file into the posts directory, and the app automatically picks it up in the table of contents on the next refresh. The app.py file looks like this:

from flask import Flask, render_template, abort
import markdown
import yaml
from pathlib import Path
from datetime import datetime
from collections import defaultdict
import re

app = Flask(__name__)
POSTS_DIR = Path(__file__).parent / "posts"

def slugify(title):
    return re.sub(r'[^a-z0-9]+', '-', title.lower()).strip('-')


def parse_markdown_file(post_file):
    content = post_file.read_text(encoding="utf-8")
    if content.lstrip().startswith("---"):
        parts = content.split("---", 2)
        if len(parts) >= 3:
            _, fm, body = parts
            meta = yaml.safe_load(fm) or {}
        else:
            meta = {}
            body = content
    else:
        meta = {}
        body = content

    html_content = markdown.markdown(body, extensions=['fenced_code', 'codehilite'])
    title = meta.get("title", "Untitled")
    subtitle = meta.get("subtitle", "")
    slug = slugify(title)

    return {
        "title": title,
        "subtitle": subtitle,
        "slug": slug,
        "date": meta.get("date", ""),
        "screenshots": meta.get("screenshots", []),
        "content": html_content,
        "filename": post_file.name,
        "categories": [c.strip() for c in str(meta.get("categories", "")).split(",") if c.strip()],
    }


def load_posts():
    posts = []
    for post_file in sorted(POSTS_DIR.glob("*.md"), reverse=True):
        if post_file.name.lower() == "about.md":
            continue
        posts.append(parse_markdown_file(post_file))
    return posts


def load_about_page():
    about_file = POSTS_DIR / "about.md"
    if not about_file.exists():
        return None

    post = parse_markdown_file(about_file)
    post["slug"] = "about"
    return post

@app.route("/")
def index():
    posts = load_posts()
    posts.sort(key=lambda p: p["title"].lower())
    categories = defaultdict(list)
    for post in posts:
        cats = post.get('categories',[])

        for cat in post["categories"]:
            categories[cat].append(post)

    for cat in categories:
        categories[cat].sort(key=lambda x: x['date'], reverse=True)

    sorted_categories = sorted(categories.items(), key=lambda x: x[0].lower())

    return render_template("index.html", categories=sorted_categories, posts=posts, blog_name="Integrations", current_year=datetime.now().year)


@app.route("/about")
def about():
    posts = load_posts()
    posts.sort(key=lambda p: p["title"].lower())
    about_post = load_about_page()
    if not about_post:
        abort(404)

    return render_template("post.html",
                           post=about_post,
                           posts=posts,
                           blog_name="Integrations",
                           current_year=datetime.now().year)


@app.route("/post/<slug>")
def post(slug):
    posts = load_posts()
    posts.sort(key=lambda p: p["title"].lower())
    post = next((p for p in posts if p["slug"] == slug), None)
    if not post:
        abort(404)
    return render_template("post.html",
                           post=post,
                           posts=posts,
                           blog_name="Integrations",
                           current_year=datetime.now().year)

if __name__ == "__main__":
    app.run(debug=True)

The app reads all the .md files in the directory, locates their title and associated tags, and then dynamically generates a sorted list for each tag category.

The load_posts() helper function parses the markdown file and enables code highlights. Posts inherit the base.html template which looks like this:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{{ blog_name }}</title>
  <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&family=Roboto+Slab:wght@400;700&display=swap" rel="stylesheet">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/powershell.min.js"></script>
  <script>hljs.highlightAll();</script>
  <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<button id="sidebarToggle">☰</button>
  <div id="sidebar" class="sidebar"> 
    <h3>Posts</h3>
    <ul>
    {% for post in posts %}
      <li>
      <a href="{{ url_for('post', slug=post.slug) }}">{{ post.title }}</a>
      </li>
    {% endfor %}
    </ul>
    <a href="{{ url_for('index')}}">🏠</a>
    <a href="{{ url_for('about') }}">About</a>
  </div>
  <header>
    <h1>{{ blog_name }}</h1>
  </header>
  <main>
    {% block content %}{% endblock %}
  </main>
  <footer>
    &copy; {{ current_year }} {{ blog_name }}
  </footer>
  <script src="{{ url_for('static', filename='js/scripts.js') }}"></script>
    <script>
        document.getElementById("sidebarToggle").addEventListener("click", function() {
        document.getElementById("sidebar").classList.toggle("open");
        });
    </script>

    <script>
      window.va = window.va || function () { (window.vaq = window.vaq || []).push(arguments); };
    </script>
    <script defer src="/_vercel/insights/script.js"></script>
</body>
</html>

Post content is then loaded into the post.html template which looks like this:

{% extends "base.html" %}

{% block content %}
  <article>
    <h2 style="margin-bottom: -20px;">{{ post.title }}</h2>
    <h4 style="margin-bottom: -20px;">{{ post.subtitle }}</h4>
    <h5 style="color: #FFFFFF; font-weight: 100; font-style: italic;">{{ post.date }}</h5>
    <div>
      {{ post.content|safe }}
    </div>
  </article>
{% endblock %}

Markdown files are simple, and look like this:

---
title: Title Here
subtitle: Subtitle
date: 2025-09-08
---
<center><img src="/static/images/01_test.png" alt="Stub" style="width:50%;"></center>


![Stub](/static/images/01_test.png)

Note that images can be loaded with HTML or using markdown syntax as shown in the two examples above.