IT Engineer · Tiximax JSCAug 2026 – now
Making an AI CMS multi-tenant
Hardening workspace isolation, extending the LLM pipeline and reworking the schema as an in-house AI SEO CMS became a multi-workspace SaaS.
- 850+
- main-branch commits
- 445
- API routes audited
- 43
- isolation gaps closed
- 368
- test cases, 341 automated
Context
TixiGen is an AI content CMS built in-house at Tiximax: SEO planning, AI-generated articles and images, a rich-text editor, SEO scoring, review and scheduled publishing to multiple sites and languages.
It began as an in-house tool for a single company. Turning it into a multi-workspace SaaS meant that assumptions of a single tenant — in routes, prompts, defaults and data — had to be found and replaced across the codebase.
What I did
I joined the CMS in mid-August and became its second-largest contributor. Colleagues had already built the multi-tenant foundation, SSO, billing integration, CI/CD and the core of the LLM pipeline; my work sat on top of that.
- Led the workspace-isolation hardening: audited 445 API routes, closed 43 cross-workspace read/write gaps and wrote a 368-case test suite (341 automated).
- Shipped multi-workspace accounts, per-workspace subdomains, invite links and public sign-up.
- Built a customer-facing content API after comparing 10 headless CMSs against the OWASP API Top 10: scoped, expiring per-workspace keys with a 3-day rotation grace period.
- Extended the LLM pipeline: made the Claude API the primary text provider, generated Gemini response schemas from Zod, added embedding retrieval with LLM re-ranking and a verified-facts set to block invented statistics.
- Implemented a 60+-criterion SEO rubric from the business spec, with a live scorecard in the editor and language-aware thresholds (Vietnamese syllables, Japanese characters, rules for Indonesian and Tagalog).
- Re-architected PostgreSQL around a trigger-enforced content tree, replaced string-built SQL with parameter binding, and localized the UI into Vietnamese, English and Japanese.
How it works
Every query filters by a workspace key taken from the token or request context, never from user input. The workspace is resolved from the subdomain, and a user who belongs to several workspaces switches the active one through the token.
To prove isolation I did not rely on code review alone. Each audit round followed the same steps:
- Snapshot row counts per workspace across 241 tables.
- Call 112 GET endpoints as two different workspaces and compare what each can see.
- Fix every read, write, update or delete that crossed a workspace boundary, including cron jobs, social posting and MCP tools.
- Re-run the snapshot and the 368-case suite against a real QC database.
Results
In about 2.5 months I landed 850+ commits on the main branch across 41 tickets and 50+ merge requests, with 120+ SQL migrations and 130+ test files.
- 445 routes audited, 43 isolation gaps closed, 368 test cases (341 automated).
- Multi-workspace accounts covered by 77 API integration tests and 26 UI tests.
- Schema reduced from 149 to 137 tables; 19 tables renamed in one transaction.
- Editor SEO analysis cut from 53.6 ms to 22.4 ms per pass on a ~2,700-word article, with identical scores.
- UI localized into three languages, 5,804 keys each, stored in the database so translations change without a redeploy.
- News-article prompt shortened by 40% (6,499 to 3,879 characters).
What I'd do next
The isolation work was retrofitted onto an existing codebase, which is why it needed three audit rounds. Next time I would put the workspace check into a shared data-access layer from day one, so a new route cannot skip it, and keep the cross-workspace snapshot as a CI check rather than a manual audit.
Đưa CMS AI lên đa workspace
Siết cô lập dữ liệu, mở rộng pipeline LLM và tái cấu trúc schema khi một CMS SEO nội bộ chuyển thành SaaS nhiều workspace.
- 850+
- commit trên nhánh chính
- 445
- route API đã rà
- 43
- chỗ hở cô lập đã sửa
- 368
- testcase, 341 tự động
Bối cảnh
TixiGen là CMS nội dung AI do Tiximax tự phát triển: lên kế hoạch SEO, sinh bài và ảnh bằng AI, soạn thảo, chấm điểm SEO, duyệt bài và đăng theo lịch lên nhiều site, nhiều ngôn ngữ.
Ban đầu đây là công cụ nội bộ cho một công ty. Để thành SaaS nhiều workspace, mọi giả định chỉ có một tenant — trong route, prompt, cấu hình mặc định và dữ liệu — phải được tìm ra và thay thế trên toàn bộ codebase.
Phần việc của tôi
Tôi vào dự án CMS từ giữa tháng 8 và trở thành người đóng góp nhiều thứ hai. Nền multi-tenant, SSO, tích hợp thanh toán, CI/CD và lõi pipeline LLM do đồng nghiệp làm trước; phần của tôi xây tiếp trên nền đó.
- Phụ trách siết cô lập workspace: rà 445 route API, sửa 43 chỗ đọc/ghi chéo giữa các workspace, viết bộ 368 testcase (341 tự động).
- Làm tài khoản thuộc nhiều workspace, subdomain riêng cho từng workspace, link mời và đăng ký công khai.
- Xây API cho khách sau khi so sánh 10 headless CMS và đối chiếu OWASP API Top 10: API key theo workspace, giới hạn phạm vi, có hạn dùng, xoay khoá có 3 ngày ân hạn.
- Mở rộng pipeline LLM: đưa Claude API lên làm provider chính, sinh responseSchema cho Gemini từ Zod, gợi ý ngữ cảnh bằng embedding rồi cho LLM xếp hạng lại, thêm bộ số đã xác minh để chặn AI bịa số liệu.
- Cài đặt rubric SEO 60+ tiêu chí theo đặc tả nghiệp vụ, phiếu điểm cập nhật ngay trong editor, ngưỡng theo ngôn ngữ (tiếng Việt đếm âm tiết, tiếng Nhật đếm ký tự, ngưỡng riêng cho Indonesia và Tagalog).
- Tái cấu trúc PostgreSQL quanh cây nội dung có trigger giữ toàn vẹn, thay SQL ghép chuỗi bằng tham số hoá, và bản địa hoá giao diện sang tiếng Việt, Anh, Nhật.
Cách hoạt động
Mọi truy vấn đều lọc theo workspace key lấy từ token hoặc request context, không lấy từ dữ liệu người dùng gửi lên. Workspace được phân giải theo subdomain; tài khoản thuộc nhiều workspace đổi workspace đang dùng thông qua token.
Để chứng minh dữ liệu đã tách bạch, tôi không chỉ đọc code. Mỗi đợt rà soát làm theo các bước:
- Chụp số dòng theo từng workspace trên 241 bảng.
- Gọi 112 API GET dưới góc nhìn của hai workspace khác nhau rồi so kết quả.
- Sửa mọi thao tác đọc, ghi, sửa, xoá vượt ranh giới workspace, kể cả cron, đăng mạng xã hội và tool MCP.
- Chụp lại số dòng và chạy bộ 368 testcase trên DB QC thật.
Kết quả
Trong khoảng 2,5 tháng: 850+ commit trên nhánh chính, 41 ticket, 50+ merge request, 120+ migration SQL và 130+ file test.
- Rà 445 route, sửa 43 chỗ hở cô lập, 368 testcase (341 tự động).
- Tài khoản nhiều workspace có 77 test API tích hợp và 26 test UI.
- Schema giảm từ 149 xuống 137 bảng; đổi tên 19 bảng trong một transaction.
- Phân tích SEO trong editor giảm từ 53,6 ms xuống 22,4 ms mỗi lượt với bài khoảng 2.700 từ, điểm chấm không đổi.
- Giao diện có 3 ngôn ngữ, mỗi ngôn ngữ 5.804 key, lưu trong DB nên sửa bản dịch không cần deploy lại.
- Prompt bài news ngắn đi 40% (6.499 xuống 3.879 ký tự).
Nếu làm lại
Việc cô lập được vá thêm vào một codebase đã có sẵn, nên phải rà tới ba đợt. Lần sau tôi sẽ đặt bước kiểm workspace vào một lớp truy cập dữ liệu dùng chung ngay từ đầu để route mới không thể bỏ qua, và đưa bước chụp số dòng chéo workspace vào CI thay vì rà tay.