Posts
Post Model
The Post model (replaces the API's Blog model) supports richer content management:
| Field | API Blog | CMS Post | Improvement |
|---|---|---|---|
| Status | published (boolean) + inTrash (boolean) | status (string: draft/published/scheduled/trash) | Unified status field |
| Scheduling | — | scheduledAt, publishedAt | Scheduled publishing |
| SEO | metaTitle, metaDescription | Same + jsonLdSchema | Structured data support |
| URL | canonicalUrl | canonicalUrl + urlHistory[] | Handles URL changes |
| Metadata | — | meta (JSON: wordCount, readingTime, focusKeyphrase) | Richer analytics |
| Categories | blogCategoryId (single) | blogCategoryId + subCategoryId | Subcategory support |
| Tags | tags (string) | tags (string) | Same (stored as comma-separated) |
| Geographic | cityId, country (string) | cityId, countryId | Proper Country model |
Creating a Post
The CMS admin provides a form with:
- Title — Post title
- Slug — Auto-generated from title, manually editable
- Content — Lexical rich text editor
- Cover Image — Upload or URL
- Category — Post category (blog/travel-guide) + optional subcategory
- Author — Select from existing authors
- Tags — Comma-separated
- Status — Draft, Publish, or Schedule
- SEO — Meta title, meta description, JSON-LD schema
- Location — City, country for geographic tagging
Post Listing & Filtering
The CMS admin post list supports:
- Status filter: All / Published / Draft / Scheduled / Trash
- Search: By title
- Sorting: By date, title
- Bulk actions: Move to trash, restore, delete permanently
Categories & Subcategories
Posts have a two-level category hierarchy:
PostCategory (e.g., "Travel Guide", "Blog")
└── SubCategory (e.g., "Trekking", "Culture", "Food")- A post must have a
PostCategory(blog or travel-guide) - A post may optionally have a
SubCategory - Categories determine the URL path:
/{country}/{category.slug}/{post.slug} - Category "blog" maps to
/blog/routes; "travel-guide" maps to/travel-guide/routes
Public API Endpoints
Single Post
GET /api/posts/{slug}?country=nepal&category=travel-guideReturns the post with full content, author info, related posts (same category), and tag-based "You may also like" recommendations. Increments the view counter.
Published Posts
GET /api/posts/published?page=1&limit=20&search=trek&category=blogReturns published posts with pagination. Supports search and category filtering. Used by the frontend for blog listings and sitemap generation.
Posts by Category
GET /api/posts/category/blog?page=1&limit=10
GET /api/posts/category/travel-guide?page=1&limit=10Used by the frontend for blog and travel-guide listing pages.
Slug Resolution
GET /api/posts/resolve/{slug}?country=nepal&category=travel-guideReturns { exists: true, canonicalPath, slug }. Also checks urlHistory for renamed posts. Used by the catch-all route to find posts.
URL Structure
The CMS generates canonical URLs using this logic:
1. Start with slug: /{post.slug}
2. If category exists, prepend: /{category.slug}/{post.slug}
3. If country exists, prepend: /{country.countryHandle}/{category.slug}/{post.slug}
4. Result: /nepal/travel-guide/best-time-to-visitURL history is tracked — when a slug changes, the old URL is stored in urlHistory[] and the resolve endpoint still finds the post.