Services

2026 04 05 Learn To Academy


Learn to Academy Migration

Date: 2026-04-05 Status: Completed Branch: feat/refactor-learn-to-academy

Summary#

Renamed the training platform from "learn" to "academy" for clearer branding and consistent product positioning.

Changes#

Frontend App#

  • Directory: apps/learnapps/academy
  • Package name: learnacademy
  • Dev port: 3044 (unchanged)
  • Prod port: 3054 → 13007 (new allocation from reserved range)
  • Wrangler project: assistance-learnassistance-academy

Backend Service#

  • Service name: learn-apiacademy-api
  • Directory: backend/cmd/learn-apibackend/cmd/academy-api
  • Internal package: backend/internal/learnbackend/internal/academy
  • Seed service: learn-seedacademy-seed
  • Dev port: 3065 (unchanged)
  • Prod port: 3066 (unchanged)
  • Health endpoint: Returns {"service":"academy-api","status":"ok"}

Manager Admin#

  • Routes: /admin/learn/admin/academy
  • Route files: apps/manager/src/routes/_admin/admin/learn/academy/
  • Query keys: ["admin", "learn", ...]["admin", "academy", ...]
  • API endpoints: /admin/learn/*/admin/academy/*

Configuration Files#

  • package.json: All --filter=learn--filter=academy (9 occurrences)
  • turbo.json: learn#build, learn#testacademy#build, academy#test
  • .mise.toml: 50+ task updates (dev:learn → dev:academy, etc.)
  • podman-compose.yml: learn-api service → academy-api

DNS & Infrastructure#

  • Dev domain: academy.assistance.dev.assistance.bg → 139.162.191.120
  • Prod domain: academy.assistance.prod.assistance.bg → 139.162.191.120
  • Caddy config: Site blocks added on both public and LAN gateways
  • TLS certificates: Valid Let's Encrypt certificates obtained
  • Split-horizon DNS: LAN resolution to 192.168.5.10 (local Caddy)

URL Changes#

  • robots.ts: learn.assistance.prod.assistance.bgacademy.assistance.prod.assistance.bg
  • sitemap.ts: Same URL update
  • course-json-ld.tsx: Same URL update

Breaking Changes#

DNS Records#

Old DNS records were replaced (direct cutover):

  • learn.assistance.dev.assistance.bg (removed)
  • learn.assistance.prod.assistance.bg (removed)
  • academy.assistance.dev.assistance.bg (added)
  • academy.assistance.prod.assistance.bg (added)

API Endpoints#

Backend API routes changed:

  • Old: /admin/learn/courses, /admin/learn/enrollments, etc.
  • New: /admin/academy/courses, /admin/academy/enrollments, etc.

Manager Admin#

Bookmarks and direct links to /admin/learn need updating to /admin/academy.

Environment Variables#

No breaking changes - NEXT_PUBLIC_LEARN_API_URL still supported for backward compatibility.

Rollback Procedure#

If critical issues are discovered:

Quick Rollback (< 10 minutes)#

  1. Revert DNS:
bash
1
# Via registry API
2
curl -X POST "http://192.168.3.20:9070/domains/3451891/records" \
3
-H "Content-Type: application/json" \
4
-d '{"type":"A","name":"learn.assistance","target":"139.162.191.120","ttl_sec":300}'
5
6
curl -X POST "http://192.168.3.20:9070/domains/3451909/records" \
7
-H "Content-Type: application/json" \
8
-d '{"type":"A","name":"learn.assistance","target":"139.162.191.120","ttl_sec":300}'
  1. Revert Caddy:
bash
1
ssh 192.168.3.20 'cd /home/vchavkov/src/BA/registry/caddy && \
2
cp Caddyfile.backup-learn Caddyfile && \
3
bash scripts/sync.sh'
  1. Restart services with old binaries:
bash
1
cd /home/vchavkov/src/assistance
2
git checkout main
3
systemctl restart learn-api # If running as systemd service

Full Rollback (< 30 minutes)#

bash
1
# Checkout main branch
2
git checkout main
3
4
# Restore directory structure
5
cd apps/
6
mv academy learn
7
8
# Reset all configs
9
git checkout main -- package.json .mise.toml turbo.json podman-compose.yml
10
11
# Reinstall dependencies
12
pnpm install
13
14
# Rebuild
15
cd backend && make build SERVICE=learn-api
16
cd ../apps/learn && pnpm build
17
18
# Redeploy
19
mise run cf:deploy:learn

Verification#

Technical Checks#

  • All builds pass without errors
  • All tests pass (pnpm test)
  • Dev environment starts cleanly (mise run dev:academy)
  • Prod deployment successful
  • No 500 errors in logs
  • DNS resolves correctly
  • Caddy routing works
  • Manager admin panel functional

Functional Checks#

  • Course catalog loads
  • Search functionality works
  • User enrollment flow works
  • Progress tracking works
  • Certificate generation works
  • Manager admin can list/edit courses
  • Analytics/telemetry flowing

Infrastructure Checks#

  • Dev DNS: academy.assistance.dev.assistance.bg → 139.162.191.120 ✓
  • Prod DNS: academy.assistance.prod.assistance.bg → 139.162.191.120 ✓
  • Caddy dev proxy: port 3044 ✓
  • Caddy prod proxy: port 13007 ✓
  • Backend API health check: {"service":"academy-api","status":"ok"}
  • TLS certificates: Valid Let's Encrypt certificates ✓
  • HTTPS endpoints: Both dev and prod responding ✓

Testing Results#

Dev Environment#

bash
1
# Frontend
2
curl -I https://academy.assistance.dev.assistance.bg/trainings
3
# HTTP/2 200 ✓
4
5
# Backend
6
curl http://localhost:3065/health
7
# {"service":"academy-api","status":"ok"} ✓
8
9
# Manager
10
curl http://localhost:3045/admin/academy
11
# HTTP/1.1 200 ✓

Build Verification#

bash
1
# Frontend build
2
pnpm --filter academy build
3
# ✓ Compiled successfully
4
5
# Backend build
6
make build SERVICE=academy-api
7
# ✓ Binary: backend/bin/academy-api (39MB)
8
9
# Cloudflare build
10
mise run cf:build:academy
11
# ✓ OpenNext artifacts generated

Files Changed#

Core Implementation (100+ files)#

  • apps/academy/ (renamed from apps/learn/)
  • backend/cmd/academy-api/ (renamed from learn-api/)
  • backend/internal/academy/ (renamed from learn/)
  • apps/manager/src/routes/_admin/admin/academy/ (renamed from learn/)

Configuration (5 files)#

  • package.json - npm scripts and filters
  • turbo.json - build task definitions
  • .mise.toml - 50+ task updates
  • podman-compose.yml - service name and image
  • apps/academy/wrangler.jsonc - Cloudflare project name

URLs (3 files)#

  • apps/academy/app/robots.ts
  • apps/academy/app/sitemap.ts
  • apps/academy/components/course-json-ld.tsx

Infrastructure (external)#

  • DNS: 2 A records added via registry API
  • Caddy: 2 site blocks added (public + LAN gateways)

Cloudflare Workers Deployment#

Status: BLOCKED#

Deployment to Cloudflare Workers is blocked pending KV namespace configuration:

Issue: wrangler.jsonc requires a KV namespace ID for Next.js incremental cache Error: No KV binding "NEXT_INC_CACHE_KV" found Workaround: Removed KV namespace from config, but OpenNext requires it

Resolution needed:

  1. Set CLOUDFLARE_API_TOKEN environment variable
  2. Run wrangler kv namespace create NEXT_INC_CACHE_KV
  3. Update apps/academy/wrangler.jsonc with namespace ID
  4. Retry deployment: npx wrangler deploy

Current workaround: Academy is deployed locally and accessible via Caddy reverse proxy at dev and prod domains. Full Cloudflare Workers deployment pending KV setup.

Post-Migration Monitoring#

First 48 Hours#

  • Monitor error rates (target: < 1%)
  • Monitor response times (p95 target: < 500ms)
  • Check logs for unexpected errors
  • Verify analytics tracking
  • Test all critical user paths (dev environment verified)

Cleanup (After 1 Week)#

  • Complete Cloudflare Workers deployment (pending KV setup)
  • Remove old Cloudflare Workers project (assistance-learn)
  • Clean up old Docker images (learn-api:latest)
  • Update team documentation/runbooks
  • Archive backup branches

References#

  • Planning doc: /home/vchavkov/.claude/plans/recursive-shimmying-turing.md
  • Feature branch: feat/refactor-learn-to-academy
  • Backup branch: backup/learn-to-academy-20260405-1816
  • Port allocation: ~/.config/brain/claude/rules/reserved-ports.md

Lessons Learned#

What Went Well#

  • Auto-checkpoint commits preserved all intermediate states
  • Comprehensive planning prevented missed references
  • Direct cutover DNS strategy worked without issues
  • TLS certificate automation (Let's Encrypt) worked seamlessly

Challenges#

  • Next.js 15 params type change required async/await fix
  • Local LAN Caddy needed separate configuration
  • Backend hostname resolution required IP address in Caddy config

Recommendations#

  • Always verify both public and LAN Caddy configurations
  • Test TLS certificate issuance before announcing availability
  • Use registry API for DNS changes (faster than manual Linode UI)