Skip to content

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?

ProblemSolution
Blog CRUD competed with commerce endpointsSeparate CMS with dedicated API
Blog authors needed rich content toolsCMS has Lexical editor, scheduling, AI generation
Content deployments required API restartsIndependent deployments
Blog schema changes risked commerce dataSeparate CMS database
Content editors authenticated through same system as suppliersDedicated CMS auth

What Was Migrated

API FeatureAPI SourceCMS Equivalent
Blog CRUDcontrollers/blog.controller/app/api/posts/
Blog Categoriescontrollers/blogCategory.controller/app/api/post-categories/
Authorscontrollers/author.controller/app/api/authors/
Blog Generationcontrollers/blogGeneration.controller/app/api/generate/
Claude AIservices/claude.service.tslib/claude.ts
City CRUD (editorial)controllers/city.controller/app/api/cities/

Files Removed from API

Controllers (19 files)

DirectoryFiles 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)

FileEndpoints Removed
routes/blog.route.ts12 endpoints (CRUD, publish, resolve, restore, category)
routes/blogCategoryRoute.ts4 endpoints (CRUD)
routes/author.route.ts5 endpoints (CRUD + login)
routes/blogGeneration.route.ts17 endpoints (import, jobs, rows, links, sync)

Route Registrations Updated

routes/index.route.ts — Removed exports:

typescript
// 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:

typescript
// 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)

FileReason
services/claude.service.tsReplaced by CMS lib/claude.ts with improved batch API support
services/syncLinkBank.tsSynced blogs and cities to BusinessLink table — feature removed

Cron Jobs (1 file)

FileReason
cron/blogGeneration.cron.tsBlog generation cron (import/row/job processing) — replaced by CMS polling

server.ts — Removed cron import and initialization:

typescript
// REMOVED:
import { initBlogGenerationCron } from "@/cron/blogGeneration.cron";
// and:
initBlogGenerationCron();

Validations (2 directories)

DirectoryFiles
validations/blog/blog.validator.ts
validations/author/author.validator.ts

Other Files

FileReason
outlines/outline.mdBlog outline template — duplicated in CMS
seeder/factories/blog.factories.tsBlog 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:

typescript
// 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:

typescript
// 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

ModelReason
BlogReplaced by Post in CMS database
BlogCategoryReplaced by PostCategory in CMS database
AuthorMigrated to CMS database
BlogGenerationImportReplaced by simplified GenerationBatch in CMS
BlogGenerationImportRowFolded into generation tracking
BlogGenerationJobFolded into generation tracking
BusinessLinkFeature removed

Removed Enums

EnumUsed By
PostTypeBlog.postType
ImportStatusBlogGenerationImport.status
GenerationStatusBlogGenerationImportRow.status
JobModeBlogGenerationJob.mode
JobStatusBlogGenerationJob.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).

prisma
// 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 HTML

After (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 HTML

Both 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 — Try getPostBySlug() first for metadata; fall back to Express API
  • ActivityPage — Try getPostBySlug() first; if found, render via PostPageComponent; 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

MetricValue
Files removed45 files
Lines removed3,347
Lines added (catch-all route)68
Controllers removed4 directories (19 files)
Routes removed4 files
Services removed2 files
Validations removed2 directories
Prisma models removed7 models
Prisma enums removed5 enums
Git branches createdfeat/remove-editorial-api (API), feat/use-cms-blog-fallback (frontend)
CompilationBoth 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:

PriorityDomainAPI FilesNotes
HighActivitiesactivity.controller/, routeCore product
HighSupplierssupplier.controller/, routeProvider management
HighBookingsbooking.controller/, route+ Stripe payments
HighAvailabilityavailability.controller/, routeActivity dates
HighReviewsreview.controller/, routePost-booking reviews
MediumTrip CategoriestripCategory.controller/, routeActivity classification
MediumTrip TypestripType.controller/, routeActivity types
MediumRegionsregion.controller/, routeGeographic regions
MediumFeatured Tagsfeatured.controller/, routePromotional tagging
MediumNewsletternewsletter.controller/, routeEmail subscriptions
MediumWishlistwishlist.controller/, routeUser saved items
MediumUsersuser.controller/, routeCustomer management
MediumAdminadmin.controller/, routeDashboard, verification
MediumLegallegal.controller/, routeTerms, policies
MediumAuthauth.controller/, middlewaresAuthentication
LowImage Proxyroutes/imageProxy.route.ts
LowMedia Librarymedia.controller/, media-library.controller/File management

Built with VitePress