CMS Overview
The CMS (cms-myeasyguide) is a standalone Next.js 16 application for managing blog posts, travel guides, authors, categories, and AI-powered content generation. It operates independently from the main Express API with its own PostgreSQL database and admin UI.
Purpose
The CMS was extracted from the main API to provide:
- Dedicated content management — Rich text editing, post scheduling, SEO management
- AI content generation — Batch blog post generation via Claude API
- Independent deployments — Content updates don't require API deployments
- Focused admin experience — Tailored for content editors, not commerce operations
Key Features
| Feature | Description |
|---|---|
| Post CRUD | Create, edit, publish, schedule, trash blog posts and travel guides |
| Rich Text Editor | Lexical-based editor with HTML output |
| Categories & Subcategories | Hierarchical content organization |
| Author Management | Author profiles, authentication, dashboard |
| AI Generation | Claude API batch generation for bulk blog creation |
| City & Country Management | Geographic tagging for content |
| Tag Management | Content tagging system |
| Image Upload | File upload for post cover images |
| Scheduled Publishing | node-cron checks every minute for scheduled posts |
| OpenAPI Spec | Auto-generated API documentation |
| SEO Tools | Meta titles/descriptions, canonical URLs, JSON-LD schema |
Admin UI
The CMS admin is at /admin and includes:
| Route | Purpose |
|---|---|
/admin/posts | Post list with status filtering |
/admin/posts/edit | Create/edit post |
/admin/posts/edit/{slug} | Edit specific post |
/admin/authors | Author CRUD |
/admin/post-categories | Category management |
/admin/subcategories | Subcategory management |
/admin/manage-cities | City management |
/admin/manage-countries | Country management |
/admin/generate | AI blog generation |
CMS API Routes
The CMS exposes a RESTful API for the admin UI and for the public frontend:
| Route | Purpose |
|---|---|
GET /api/posts/{slug} | Public: single post with related content |
GET /api/posts/published | Public: published posts (paginated, searchable) |
GET /api/posts/category/{slug} | Public: posts by category (blog/travel-guide) |
GET /api/posts/author/{id} | Public: posts by author |
GET /api/posts/resolve/{slug} | Public: resolve slug to canonical path |
GET /api/posts | Auth: all posts (admin) |
POST /api/posts | Auth: create post |
PATCH /api/posts/{slug} | Auth: update post |
DELETE /api/posts/{slug} | Auth: trash/delete post |
GET /api/posts/restore/{id} | Auth: restore from trash |
GET /api/authors | Public: list authors |
POST /api/authors | Auth: create author |
PATCH /api/authors/{id} | Auth: update author |
DELETE /api/authors/{id} | Auth: delete author |
GET /api/post-categories | Public: list categories |
POST /api/post-categories | Auth: create category |
PATCH /api/post-categories/{id} | Auth: update category |
DELETE /api/post-categories/{id} | Auth: delete category |
GET /api/cities | Public: list cities |
POST /api/cities | Auth: create city |
GET /api/countries | Public: list countries |
GET /api/tags | Public: list tags |
POST /api/upload | Auth: upload file |
POST /api/auth/login | Public: author login |
Authentication
The CMS uses JWT-based auth with the jose library:
- Authors log in via
POST /api/auth/login - JWT stored in httpOnly cookie
- Protected routes use
requireAuth()fromlib/auth.ts - Session retrieval via
getSession()from cookies
Scheduled Publishing
The CMS runs a node-cron job (initialized in instrumentation.ts) that runs every minute:
- Queries for posts where
status = "scheduled"andscheduledAt <= now - Updates status to
"published"and setspublishedAt - Generates canonical URL
Post Status Lifecycle
┌─────────┐
│ DRAFT │
└────┬────┘
│
▼
┌───────────┐
│ SCHEDULED │──(cron)──▶ ┌───────────┐
└───────────┘ │ PUBLISHED │
└─────┬─────┘
┌─────────┐ │
│ TRASH │◀──────────────┘
└─────────┘Separated from API
The CMS was originally part of the Express API. The Editorial Migration page documents the full extraction process, including which API files were removed and why.