Skip to content

ava-sig/blog

Repository files navigation

Glyphic Blog

This monorepo contains:

  • apps/web: Nuxt 3 SSR blog UI
  • apps/api: Express API with file-backed storage (data/ uploads)

Use the production Docker setup below to deploy quickly with Docker Hub images and Nginx.

Governance & Ritual (public summary)

  • Use feature branches and conventional commits.
  • Before commit/PR:
    • Run tests and guards: npm run test:all
    • Ensure hidden-layer content (docs/, internal/, glyphs/) is not staged.
  • Open PRs using the provided template; include governance adherence and runtime ports.

Sprint End Ritual

  • Merge the feature branch to main when all CI checks are green.
  • Append the latest changes to the Changelog section in this README.md.
  • Create and push an annotated tag for the release:
    VERSION=v0.2.3
    git tag -a "$VERSION" -m "release: $VERSION"
    git push origin "$VERSION"
  • Delete the merged branch locally and on origin:
    BR=feat/v0.2.3-seo-security-editor
    git branch -d "$BR" || true
    git push origin --delete "$BR" || true
  • Deploy production images (Docker Hub pull + compose up):
    # Ensure .env.prod is present (see .env.prod.example)
    docker compose --env-file .env.prod -f docker-compose.prod.yml pull
    docker compose --env-file .env.prod -f docker-compose.prod.yml up -d
  • Post-merge smoke test: confirm .github/workflows/post-merge-smoke.yml passes against the remote API URL (checks auth, basic endpoints, and og:image).
  • Governance upkeep: keep branch protection required checks in sync with current workflow job names to avoid stale/phantom failures.

Optional local pre-commit hook

Create a local git hook to block commits if checks fail:

mkdir -p .githooks
cat > .githooks/pre-commit <<'SH'
#!/usr/bin/env bash
set -euo pipefail
npm run test:all
SH
chmod +x .githooks/pre-commit
git config core.hooksPath .githooks

This repository avoids committing private governance/glyph content; CI enforces guards.


Quick Deploy (Docker Hub: ava2016)

Deploy the latest release using prebuilt images from Docker Hub namespace ava2016.

  1. Create .env.prod next to docker-compose.prod.yml (use .env.prod.example as a base):
# API
JWT_SECRET=super_secret_value
CORS_ORIGIN=https://your.site
PUBLIC_BASE_URL=https://api.your.site

# Web (browser calls API here)
NUXT_PUBLIC_API_BASE=https://api.your.site
  1. Pull and start services (from repo root):
docker compose --env-file .env.prod -f docker-compose.prod.yml pull
docker compose --env-file .env.prod -f docker-compose.prod.yml up -d
docker compose -f docker-compose.prod.yml ps

Notes

  • Web runs in the container on PORT=5000; expose via Nginx or port mapping (e.g., 5588:5000).
  • API runs on 3000; expose via Nginx or mapping (e.g., 3388:3000).
  • The production compose file runs both containers as non-root, drops Linux capabilities, and keeps the root filesystem read-only.
  • Current web image includes the Nuxt vite-builder runtime fix and stable Nitro start.

Rollback

# If you tag images per release, set TAG (e.g., v0.2.4) in .env.prod or compose file
docker compose --env-file .env.prod -f docker-compose.prod.yml pull
docker compose --env-file .env.prod -f docker-compose.prod.yml up -d

Production Deploy (Docker Hub + docker-compose)

We provide a production compose file: docker-compose.prod.yml.

1) Build and push images (multi-arch recommended)

Replace DOCKER_NS with your Docker Hub namespace.

# Web (Nuxt SSR)
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t DOCKER_NS/glyph-web:vX.Y.Z \
  -t DOCKER_NS/glyph-web:latest \
  -f apps/web/Dockerfile apps/web --push

# API (Express)
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t DOCKER_NS/glyph-api:vX.Y.Z \
  -t DOCKER_NS/glyph-api:latest \
  -f apps/api/Dockerfile apps/api --push

2) Server configuration

  • Ensure Docker + docker compose v2, and Nginx as reverse proxy.
  • DNS:
    • Web: yourdomain.com -> server IP
    • API: api.yourdomain.com -> server IP

Create .env.prod next to docker-compose.prod.yml (see .env.prod.example):

DOCKER_NS=your-dockerhub-user
TAG=vX.Y.Z
NUXT_PUBLIC_API_BASE=https://api.yourdomain.com
CORS_ORIGIN=https://yourdomain.com
PUBLIC_BASE_URL=https://api.yourdomain.com
JWT_SECRET=change-me

3) Run the stack

docker compose --env-file .env.prod -f docker-compose.prod.yml pull
docker compose --env-file .env.prod -f docker-compose.prod.yml up -d
docker compose -f docker-compose.prod.yml ps

The API persists uploads in the named volume api_data mounted at /app/data. No database service is required for the current production app.

3.5) DigitalOcean Droplet checklist

  • Create an Ubuntu Droplet with Docker Engine and Docker Compose plugin installed.
  • Create a non-root deploy user and add it to the docker group.
  • Copy docker-compose.prod.yml and .env.prod to the server.
  • Log in to Docker Hub on the Droplet with docker login.
  • Keep only ports 80 and 443 open publicly; the compose file binds app ports to 127.0.0.1 only.
  • Pull and start with docker compose --env-file .env.prod -f docker-compose.prod.yml up -d.
  • Put Nginx in front of 127.0.0.1:5000 and 127.0.0.1:3000, then issue TLS certificates with Let's Encrypt.
  • Store JWT_SECRET only in .env.prod on the server and rotate it if a token is ever exposed.

4) Nginx reverse proxy

Terminate TLS at Nginx and proxy to the internal ports:

  • Web: https://yourdomain.com -> http://127.0.0.1:5000
  • API: https://api.yourdomain.com -> http://127.0.0.1:3000

Minimal server blocks:

server { listen 80; server_name yourdomain.com; return 301 https://$host$request_uri; }
server { listen 80; server_name api.yourdomain.com; return 301 https://$host$request_uri; }

server {
  listen 443 ssl; server_name yourdomain.com;
  ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
  location / { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://127.0.0.1:5000; }
}

server {
  listen 443 ssl; server_name api.yourdomain.com;
  ssl_certificate /etc/letsencrypt/live/api.yourdomain.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/api.yourdomain.com/privkey.pem;
  client_max_body_size 20m;
  location / { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://127.0.0.1:3000; }
  location /uploads/ { proxy_pass http://127.0.0.1:3000; expires 7d; add_header Cache-Control "public, max-age=604800, immutable"; }
}

5) Social card check

Set NUXT_PUBLIC_API_BASE and PUBLIC_BASE_URL to the public API domain so og:image uses absolute URLs. Validate a post URL with Twitter Card Validator after deployment.


Local development

# From repo root
npm run test:all

# Web dev
cd apps/web && npm i && npm run dev

# API dev
cd ../api && npm i && npm run dev

Local production smoke test

Build the hardened images locally and run the production compose stack with localhost-friendly env values:

cp .env.prod.example .env.prod.local

Edit .env.prod.local to use:

DOCKER_NS=local
TAG=dev
WEB_DOMAIN=localhost
API_DOMAIN=localhost
NUXT_PUBLIC_API_BASE=http://127.0.0.1:3000
CORS_ORIGIN=http://127.0.0.1:5000,http://localhost:5000
PUBLIC_BASE_URL=http://127.0.0.1:3000
JWT_SECRET=change-me-now

Then build and run:

docker build -t local/glyph-api:dev -f apps/api/Dockerfile apps/api
docker build -t local/glyph-web:dev -f apps/web/Dockerfile apps/web
docker compose --env-file .env.prod.local -f docker-compose.prod.yml up -d
docker compose --env-file .env.prod.local -f docker-compose.prod.yml ps
curl -i http://127.0.0.1:3000/api/health
curl -I http://127.0.0.1:5000/

E2E Testing (Playwright)

The E2E tests run the Nuxt app locally and target the API running in Docker.

  • The frontend server runs on FRONTEND_PORT (default 5999).
  • The tests and the Nuxt app talk to the Docker-exposed API via API_BASE (default http://localhost:3388).
  • Real JWT tokens are generated in tests using JWT_SECRET (HS256).

Required environment variables

Add these to your .env (see .env.example):

JWT_SECRET=change_me_in_local_env
API_BASE=http://localhost:3388
FRONTEND_PORT=5999
# Optional: used by Nuxt runtime as well
NUXT_PUBLIC_API_BASE=http://localhost:3388

Run E2E locally

# 1) Start API (in another terminal)
docker compose up -d api

# 2) From repo root, run Playwright tests
cd apps/web
npm i
npx playwright install --with-deps
npm run test:e2e

Notes:

  • Tests will skip if JWT_SECRET is missing.
  • The Playwright config loads env from the repo root .env and starts only Nuxt.
  • If port 5999 is busy, set FRONTEND_PORT to another free port in .env.

CI behavior

  • .github/workflows/ci.yml defines an e2e job that:
    • Brings up api via docker compose (API exposed on host :3388).
    • Seeds a CI .env with JWT_SECRET (from E2E_JWT_SECRET secret), API_BASE, and NUXT_PUBLIC_API_BASE.
    • Installs Playwright browsers and runs the E2E suite with the frontend on FRONTEND_PORT=5999.
    • Tears down Docker after completion.

Changelog

v0.2.20 — 2026-05-07

  • Web (SEO):
    • Replace the generic homepage Posts metadata with the configured blog name, a real meta description, stronger Open Graph and Twitter fields, and Blog structured data in SSR HTML.
    • Use the newest post’s first image as the homepage social image when available.
  • Web (markdown media):
    • Add ![video](youtube-url) support that renders a responsive embedded YouTube player.
    • Keep social-image extraction focused on actual images by ignoring video embeds.
  • Tests / build:
    • Extend content tests for YouTube video embeds.
    • Web typecheck, unit tests, and production build pass.
  • Deploy:
    • Refresh .env.prod.example to point to v0.2.20.

v0.2.19 — 2026-05-03

  • Web (paste uploads):
    • Fix clipboard image pastes so multiple pasted local photos are uploaded and inserted, instead of only the first image being picked up.
    • Prevent raw local filesystem paths from being pasted into the editor when image files are present on the clipboard.
  • Web (type safety):
    • Add local typing for markdown-it and tighten post/metric typing so strict Nuxt typecheck passes in CI.
  • Tests / build:
    • Extend paste-media tests for multi-image clipboard uploads.
    • Web typecheck and unit tests pass.
  • Deploy:
    • Refresh .env.prod.example to point to v0.2.19.

v0.2.18 — 2026-05-03

  • Web (reading flow):
    • Treat a standalone --- line inside a post as a homepage teaser cutoff so feed cards stay shorter and defer the rest of the post to the single-post page.
    • Add a soft fade and Read More action on truncated homepage cards.
  • Web (auth UX):
    • Make the JWT auth modal submit on Enter instead of inserting a newline.
  • Tests / build:
    • Extend web content tests for teaser splitting.
    • Web unit tests and production build pass.
  • Deploy:
    • Refresh .env.prod.example to point to v0.2.18.

v0.2.17 — 2026-05-03

  • Web (stability):
    • Make homepage cards visible by default and only apply reveal hiding to genuinely off-screen cards, preventing the feed from appearing blank until interaction or late hydration.
    • Normalize single-post timestamp formatting to a fixed locale and UTC timezone so SSR and hydration produce the same text.
    • Remove the transient Post not found flash when navigating back from a post to the homepage during route teardown.
  • Deploy:
    • Refresh .env.prod.example to point to v0.2.17.

v0.2.16 — 2026-05-03

  • Web (markdown):
    • Replace the hand-rolled content regex renderer with a real Markdown pipeline using markdown-it.
    • Support headings, lists, emphasis, paragraphs, links, and explicit Markdown images while preserving existing upload image behavior.
    • Stop treating bare image URLs as images; image intent is now explicit through ![](...).
  • Web (metadata):
    • Add NUXT_PUBLIC_SITE_URL and use it for absolute canonical and Open Graph URL generation instead of relying on inferred request origin.
  • Tests / build:
    • Update content and slug tests for the new Markdown and URL behavior.
    • Web unit tests and production build pass with the new renderer.
  • Deploy:
    • Refresh .env.prod.example to point to v0.2.16.

v0.2.15 — 2026-05-02

  • Web (social cards):
    • Make SSR post URLs resolve against forwarded host/protocol headers so canonical URLs and og:url are emitted as absolute URLs on the live site.
    • This brings the social metadata into a fully consistent shape for X/Twitter previews alongside the already-correct og:image and twitter:image tags.
  • Tests:
    • Extend the slug/url test coverage for forwarded-header SSR URL generation.
  • Deploy:
    • Refresh .env.prod.example to point to v0.2.15.

v0.2.14 — 2026-05-02

  • Web (social cards):
    • Move single-post data loading and Open Graph / Twitter card metadata onto the SSR path so X can see og:image and render link previews with the post image.
  • Metrics:
    • Add persisted per-post viewed and opened counters.
    • Count viewed when a post card becomes visible on the home feed and opened when a reader enters the single-post view.
    • Show the ratio inline on home cards as viewed:opened.
  • API:
    • Add a public POST /api/posts/:id/metric endpoint for metric increments and normalize legacy posts to include zeroed metrics.
  • Deploy:
    • Refresh .env.prod.example to point to v0.2.14.

v0.2.13 — 2026-05-02

  • Web (footer):
    • Normalize the footer version label so NUXT_PUBLIC_APP_VERSION=v0.2.12 renders as v0.2.12 instead of vv0.2.12.
  • CI / governance:
    • Soft-disable governance glyph validation by default in CI and Glyph & Docs Guard while the separate ava-sig/governance repo is temporarily divergent.
    • Keep the governance-dependent jobs available behind the repo variable ENABLE_GOVERNANCE_GLYPHS=1.
  • Deploy:
    • Refresh .env.prod.example to point to v0.2.13.

v0.2.12 — 2026-05-02

  • Web (sharing):
    • Fix the X share button on the home feed so it generates absolute post URLs during SSR instead of falling back to relative /p/... links.
    • Keep single-post and home-feed share links consistent by deriving the origin from Nuxt request context on the server.
    • Update slug/share tests to cover SSR absolute URL generation.

v0.2.11 — 2026-05-02

  • Docker / production hardening:
    • Run both production containers as non-root users, keep the root filesystem read-only, drop Linux capabilities, and add container health checks in docker-compose.prod.yml.
    • Add per-app .dockerignore files to keep Docker build contexts lean and avoid shipping local artifacts into images.
    • Remove the unused Postgres service and dead database env wiring from local/prod compose, examples, docs, and CI so deploys match the file-backed API architecture.
  • API:
    • Respect PORT in src/server.js and improve startup logging.
    • Tighten CORS behavior so production uses configured origins instead of wildcard access.
    • Add basic security headers and restrict uploads to known image MIME types with clearer validation errors.
  • Web / theme defaults:
    • Keep the server defaultTheme authoritative for unauthenticated users until they explicitly choose a local preference.
    • Keep explicit browser theme choices in localStorage, but stop persisting server-provided defaults as if they were user overrides.
    • Add a focused store test covering guest defaults versus explicit local theme overrides.
  • Docs / release workflow:
    • Expand the README with a DigitalOcean droplet checklist, local production smoke-test instructions, and updated deploy guidance for the hardened Docker setup.
    • Refresh .env.prod.example to point to v0.2.11 and include NUXT_PUBLIC_APP_VERSION=${TAG} for footer version labeling.

v0.2.9 — 2025-09-12

  • API: add file-backed settings.json with { defaultTheme, blogName }; expose GET /api/settings and PUT /api/settings (auth) and ensure settings file exists in ensureStore().
  • Web (theme & persistence):
    • Persist admin-selected default theme via PUT /api/settings from header toggle.
    • Load server default theme for guests when no local preference.
    • Persist blog name on title save; load blog name for guests when no local override.
  • Web (UI):
    • Modal theme-aware styling (panel/border/buttons/textarea using theme variables).
    • Toasts readable on light theme; keep translucent look on dark theme.
    • Post list: client fallback fetch when SSR misses, then nextTick()+setupReveal() so cards render visibly; long words/URLs wrap safely.
    • New-post form, inputs, and buttons now theme-aware; card background visuals reduced to ~20% opacity.
  • Governance: centralized glyph mesh updated (removed GOV-SEC-001, added GVN-006, GVN-008).

v0.2.5 — 2025-09-09

  • API: RFC 6750-compliant auth errors (WWW-Authenticate) and specific codes (token_missing, token_invalid, token_expired, insufficient_scope, auth_required).
  • Web: surface API auth errors via toast with friendly messages; footer shows app version via NUXT_PUBLIC_APP_VERSION.
  • Docs: Quick Deploy guide for Docker Hub namespace ava2016.
  • Governance: GOV-CORE-013 Post-Merge Ritual formalized; workflow auto-deletes merged branches and posts changelog reminder.

v0.2.3 — 2025-08-31

  • CI/Vitest: remove unsupported CLI flags; scope via apps/web/vitest.config.ts test.include.
  • CI: run web-build inside apps/web; add diagnostics (commit SHA, scripts, Vitest version).
  • E2E: Dockerized API healthchecks and health wait loop for stable Playwright runs.
  • Workflows: Glyph & Docs Guard installs apps/api deps and adds diagnostics; root test:all installs API deps.
  • Post-merge smoke workflow added to validate prod-like API URL.
  • Docs: add Sprint End Ritual and changelog.
  • API: RFC 6750-compliant auth errors (WWW-Authenticate) with specific codes (token_missing, token_invalid, token_expired, insufficient_scope, auth_required).
  • Web: surface API auth errors via toast with friendly messages; add footer version label (Blog vX.Y.Z).

About

my own blog

Resources

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors