Files
Zumri-Backend/app/services/help/help.service.js
T
Sathira Sri Sathara f920ca8920
CI / test (push) Successful in 10m22s
CI / test (pull_request) Successful in 10m53s
feat: Implement Phase 10 support and recommendations features
- Added models for support messages, SLA policies, tickets, ticket events, and ticket links.
- Created routes for help, help admin, newsletter, recommendations, and support for both customer and admin.
- Developed services for help, marketing (newsletter), and recommendations.
- Introduced support service for ticket management, including creation, replies, transitions, and attachments.
- Added validation schemas for support recommendations and ticket management.
- Implemented a cron job for support reconciliation and recommendation cleanup.
- Created migration for new support and recommendation database tables.
- Added unit tests for validation and policy checks related to Phase 10 features.
2026-09-09 16:07:31 +05:30

7 lines
3.6 KiB
JavaScript

const crypto=require("crypto"),{Op}=require("sequelize"),db=require("../../models"),{resolveLocale}=require("../catalogue/locale.service");const clean=s=>String(s).replace(/<\/?[a-z][^>]*>/gi,"").trim(),pick=(rows,locale)=>rows.find(x=>x.locale===locale)||rows.find(x=>x.locale==="en")||rows[0];
async function categories(locale){const rows=await db.HelpCategory.findAll({where:{status:"ACTIVE"},order:[["sort_order","ASC"]]});return Promise.all(rows.map(async x=>{const tr=pick(await db.HelpCategoryTranslation.findAll({where:{category_id:x.id}}),locale);return{id:x.id,slug:x.slug,iconKey:x.icon_key,name:tr?.name||null,description:tr?.description||null,locale:tr?.locale||locale};}));}
async function articles({locale,categoryId,q,featured,limit=20,offset=0}){const base={status:"PUBLISHED",published_at:{[Op.lte]:new Date()},...(categoryId&&{category_id:categoryId}),...(featured!==undefined&&{is_featured:featured})},translations=q?await db.HelpArticleTranslation.findAll({where:{locale,[Op.or]:[{title:{[Op.like]:`%${q}%`}},{summary:{[Op.like]:`%${q}%`}},{body:{[Op.like]:`%${q}%`}}]},attributes:["article_id"],limit:100}):null;if(q)base.id={[Op.in]:translations.map(x=>x.article_id)};const rows=await db.HelpArticle.findAll({where:base,order:[["is_featured","DESC"],["sort_order","ASC"],["published_at","DESC"]],limit,offset});return Promise.all(rows.map(async x=>{const tr=pick(await db.HelpArticleTranslation.findAll({where:{article_id:x.id}}),locale);return{id:x.id,slug:x.slug,categoryId:x.category_id,type:x.article_type,title:tr?.title||null,summary:tr?.summary||null,body:q?undefined:tr?.body||null,locale:tr?.locale||locale,isFeatured:x.is_featured,publishedAt:x.published_at};}));}
async function createCategory(body){return db.sequelize.transaction(async t=>{const row=await db.HelpCategory.create({id:crypto.randomUUID(),slug:body.slug,status:body.status,sort_order:body.sortOrder,icon_key:body.iconKey},{transaction:t});await db.HelpCategoryTranslation.bulkCreate(body.translations.map(x=>({id:crypto.randomUUID(),category_id:row.id,locale:x.locale,name:clean(x.name),description:x.description&&clean(x.description)})),{transaction:t});return row;});}
async function createArticle(actor,body){return db.sequelize.transaction(async t=>{const category=await db.HelpCategory.findByPk(body.categoryId,{transaction:t});if(!category)throw Object.assign(new Error("Help category not found"),{status:404,code:"CATEGORY_NOT_FOUND"});const row=await db.HelpArticle.create({id:crypto.randomUUID(),slug:body.slug,category_id:body.categoryId,article_type:body.articleType,sort_order:body.sortOrder,is_featured:body.isFeatured,created_by:actor.id,updated_by:actor.id},{transaction:t});await db.HelpArticleTranslation.bulkCreate(body.translations.map(x=>({id:crypto.randomUUID(),article_id:row.id,locale:x.locale,title:clean(x.title),summary:x.summary&&clean(x.summary),body:clean(x.body),seo_title:x.seoTitle&&clean(x.seoTitle),seo_description:x.seoDescription&&clean(x.seoDescription)})),{transaction:t});return row;});}
async function publish(id,actor,status){const row=await db.HelpArticle.findByPk(id);if(!row)throw Object.assign(new Error("Help article not found"),{status:404,code:"ARTICLE_NOT_FOUND"});if(status==="PUBLISHED"&&!await db.HelpArticleTranslation.findOne({where:{article_id:id,locale:"en"}}))throw Object.assign(new Error("English translation required"),{status:409,code:"PUBLISH_REQUIREMENTS_NOT_MET"});await row.update({status,published_at:status==="PUBLISHED"?new Date():row.published_at,updated_by:actor.id});return row;}module.exports={categories,articles,createCategory,createArticle,publish,clean,pick,resolveLocale};