Editorial Migration
Overview
Blog and content management functionality was originally built into the Express API (api.myeasyguide.com). As the platform grew, these editorial features were extracted into a dedicated CMS (cms-myeasyguide) with its own database, admin UI, and API layer.
This page documents everything that was migrated, removed, or changed during this process.
Why Migrate?
| Problem | Solution |
|---|---|
| Blog CRUD competed with commerce endpoints | Separate CMS with dedicated API |
| Blog authors needed rich content tools | CMS has Lexical editor, scheduling, AI generation |
| Content deployments required API restarts | Independent deployments |
| Blog schema changes risked commerce data | Separate CMS database |
| Content editors authenticated through same system as suppliers | Dedicated CMS auth |
What Was Migrated
| API Feature | API Source | CMS Equivalent |
|---|---|---|
| Blog CRUD | controllers/blog.controller/ | app/api/posts/ |
| Blog Categories | controllers/blogCategory.controller/ | app/api/post-categories/ |
| Authors | controllers/author.controller/ | app/api/authors/ |
| Blog Generation | controllers/blogGeneration.controller/ | app/api/generate/ |
| Claude AI | services/claude.service.ts | lib/claude.ts |
| City CRUD (editorial) | controllers/city.controller/ | app/api/cities/ |
Files Removed from API
Controllers (19 files)
| Directory | Files Removed |
|---|---|
controllers/blogGeneration.controller/ | import.ts, index.ts, job.ts, links.ts, single.ts |
controllers/blog.controller/ | createBlog.ts, deleteBlog.ts, getAllBlog.ts, getBlogByAuthor.ts, getBlogByCategory.ts, getBlogBySlug.ts, getPublishedBlog.ts, getTravelGuideBySlug.ts, index.ts, purgeBlog.ts, resolveBlog.ts, restoreBlog.ts, updateBlog.ts |
controllers/blogCategory.controller/ | createBlogCategory.ts, deleteBlogCategory.ts, editBlogCategory.ts, getBlogCategories.ts, index.ts |
controllers/author.controller/ | authorLogin.ts, createAuthor.ts, deleteAuthor.ts, getAllAuthors.ts, getAuthorByUsername.ts, index.ts, updateAuthor.ts |
Routes (4 files)
| File | Endpoints Removed |
|---|---|
routes/blog.route.ts | 12 endpoints (CRUD, publish, resolve, restore, category) |
routes/blogCategoryRoute.ts | 4 endpoints (CRUD) |
routes/author.route.ts | 5 endpoints (CRUD + login) |
routes/blogGeneration.route.ts | 17 endpoints (import, jobs, rows, links, sync) |
Route Registrations Updated
routes/index.route.ts — Removed exports:
// REMOVED:
export { blogRouter } from "./blog.route";
export { blogCategoryRouter } from "./blogCategoryRoute";
export { authorRouter } from "./author.route";
export { blogGenerationRouter } from "./blogGeneration.route";routes/v1/index.route.ts — Removed imports and route registrations:
// REMOVED from imports:
// blogCategoryRouter, blogRouter, authorRouter, blogGenerationRouter
// REMOVED from registrations:
v1Router.use("/blog-category", blogCategoryRouter);
v1Router.use("/author", authorRouter);
v1Router.use("/blogs", blogRouter);
v1Router.use("/blog-generation", blogGenerationRouter);Services (2 files)
| File | Reason |
|---|---|
services/claude.service.ts | Replaced by CMS lib/claude.ts with improved batch API support |
services/syncLinkBank.ts | Synced blogs and cities to BusinessLink table — feature removed |
Cron Jobs (1 file)
| File | Reason |
|---|---|
cron/blogGeneration.cron.ts | Blog generation cron (import/row/job processing) — replaced by CMS polling |
server.ts — Removed cron import and initialization:
// REMOVED:
import { initBlogGenerationCron } from "@/cron/blogGeneration.cron";
// and:
initBlogGenerationCron();Validations (2 directories)
| Directory | Files |
|---|---|
validations/blog/ | blog.validator.ts |
validations/author/ | author.validator.ts |
Other Files
| File | Reason |
|---|---|
outlines/outline.md | Blog outline template — duplicated in CMS |
seeder/factories/blog.factories.ts | Blog seed data factory — no longer needed |
City Controller Updates
The city.controller/createCity.ts and city.controller/editCity.ts had fire-and-forget calls to syncLinkBank() after creating/updating cities. These were removed:
// REMOVED from createCity.ts:
import { syncLinkBank } from "@/services/syncLinkBank";
// and:
syncLinkBank("City", newCity.id).catch((e) =>
console.error("[syncLinkBank] City create:", e)
);
// REMOVED from editCity.ts:
import { syncLinkBank } from "@/services/syncLinkBank";
// and:
syncLinkBank("City", updatedCity.id).catch((e) =>
console.error("[syncLinkBank] City update:", e)
);Seeder Updates
The seeder (seeder/seed.ts) had functions for seeding authors, blog categories, and blogs. These were removed:
// REMOVED imports:
import { makeBlogCategory, makeBlog } from "./factories/blog.factories";
import { randSlug } from "./factories/helpers";
// REMOVED functions:
async function seedAuthors(count = 100) { ... }
async function seedBlogCategories(count = 40) { ... }
async function seedBlogs(blogCategoryIds, authorIds, count = 100) { ... }
// REMOVED calls in main():
console.log("Seeding authors...");
const authorIds = await seedAuthors();
console.log("Seeding blog categories...");
const blogCategoryIds = await seedBlogCategories();
console.log("Seeding blogs...");
await seedBlogs(blogCategoryIds, authorIds);Prisma Schema Changes
The following models and enums were removed from the API's Prisma schema (prisma/schema.prisma):
Removed Models
| Model | Reason |
|---|---|
Blog | Replaced by Post in CMS database |
BlogCategory | Replaced by PostCategory in CMS database |
Author | Migrated to CMS database |
BlogGenerationImport | Replaced by simplified GenerationBatch in CMS |
BlogGenerationImportRow | Folded into generation tracking |
BlogGenerationJob | Folded into generation tracking |
BusinessLink | Feature removed |
Removed Enums
| Enum | Used By |
|---|---|
PostType | Blog.postType |
ImportStatus | BlogGenerationImport.status |
GenerationStatus | BlogGenerationImportRow.status |
JobMode | BlogGenerationJob.mode |
JobStatus | BlogGenerationJob.status |
Updated Models
Admin — Removed blogs Blog[] relation field (no longer needed).
City — Removed blogs Blog[] relation field. The City model itself is kept because it is still referenced by Activity (commerce domain).
// Before:
model City {
...
activities Activity[]
blogs Blog[] // REMOVED
@@map("city")
}
// After:
model City {
...
activities Activity[]
@@map("city")
}Important: Tables Not Dropped
The database tables for removed models (blog, blog_category, author, blog_generation_import, blog_generation_import_row, blog_generation_job, business_link) still exist in the walkthroughnepal database. They are simply no longer managed by the API's Prisma schema.
Do not run prisma migrate from the API — it would generate a migration that drops these tables. Run migrations only when you are certain no data is needed.
Frontend Catch-All Route Update
The frontend's catch-all route (myeasyguide.com/app/(web)/[...slug]/page.tsx) had a legacy fallback that queried the Express API for blog slugs. This was updated to try the CMS first.
Before (legacy flow)
[...slug] page.tsx
└── if slug doesn't end with "-{id}":
└── fetch {API}/blogs/resolve/{slug}
└── if exists:
└── fetch {API}/blogs/{slug}
└── render inline blog HTMLAfter (CMS-first flow)
[...slug] page.tsx
└── if slug doesn't end with "-{id}":
└── getPostBySlug(slug) → CMS /api/posts/{slug}
└── if found:
└── render PostPageComponent (same as /travel-guide)
└── if not found (fallback):
└── fetch {API}/blogs/resolve/{slug}
└── if exists:
└── fetch {API}/blogs/{slug}
└── render inline blog HTMLBoth generateMetadata and ActivityPage were updated with the CMS-first pattern.
What changed in the code
The catch-all route ([...slug]/page.tsx) received +68 lines of CMS-first logic:
generateMetadata— TrygetPostBySlug()first for metadata; fall back to Express APIActivityPage— TrygetPostBySlug()first; if found, render viaPostPageComponent; fall back to Express API
The CMS's getPostBySlug() returns { post: Post | null, youMayAlsoLike: RelatedPost[] } and is already imported in the file. The PostPageComponent was already used for the /travel-guide/ prefixed routes.
Summary of Changes
| Metric | Value |
|---|---|
| Files removed | 45 files |
| Lines removed | 3,347 |
| Lines added (catch-all route) | 68 |
| Controllers removed | 4 directories (19 files) |
| Routes removed | 4 files |
| Services removed | 2 files |
| Validations removed | 2 directories |
| Prisma models removed | 7 models |
| Prisma enums removed | 5 enums |
| Git branches created | feat/remove-editorial-api (API), feat/use-cms-blog-fallback (frontend) |
| Compilation | Both projects compile clean with zero errors |
What Remains to Be Migrated
The following API features are not yet migrated to the CMS or any other dedicated service:
| Priority | Domain | API Files | Notes |
|---|---|---|---|
| High | Activities | activity.controller/, route | Core product |
| High | Suppliers | supplier.controller/, route | Provider management |
| High | Bookings | booking.controller/, route | + Stripe payments |
| High | Availability | availability.controller/, route | Activity dates |
| High | Reviews | review.controller/, route | Post-booking reviews |
| Medium | Trip Categories | tripCategory.controller/, route | Activity classification |
| Medium | Trip Types | tripType.controller/, route | Activity types |
| Medium | Regions | region.controller/, route | Geographic regions |
| Medium | Featured Tags | featured.controller/, route | Promotional tagging |
| Medium | Newsletter | newsletter.controller/, route | Email subscriptions |
| Medium | Wishlist | wishlist.controller/, route | User saved items |
| Medium | Users | user.controller/, route | Customer management |
| Medium | Admin | admin.controller/, route | Dashboard, verification |
| Medium | Legal | legal.controller/, route | Terms, policies |
| Medium | Auth | auth.controller/, middlewares | Authentication |
| Low | Image Proxy | routes/imageProxy.route.ts | |
| Low | Media Library | media.controller/, media-library.controller/ | File management |