add editable menu items for companies
This commit is contained in:
@@ -4,6 +4,7 @@ import { parseBody, parseQuery, parseParams } from '../../http/validate'
|
||||
import { ok, created } from '../../http/respond'
|
||||
import * as service from './admin.service'
|
||||
import * as subService from '../subscriptions/subscription.service'
|
||||
import * as menuService from '../menu/menu.service'
|
||||
import { presentAdminUser } from './admin.presenter'
|
||||
import {
|
||||
loginSchema, forgotPasswordSchema, resetPasswordSchema, totpVerifySchema,
|
||||
@@ -16,6 +17,8 @@ import {
|
||||
promotionCreateSchema, promotionUpdateSchema, promotionIdParamSchema,
|
||||
billingAccountUpdateSchema, createBillingInvoiceSchema, payBillingInvoiceSchema,
|
||||
retryBillingInvoiceSchema, billingReasonSchema, billingCreditNoteSchema, billingRefundSchema,
|
||||
menuItemSchema, menuItemStatusSchema, menuPlanAssignmentsSchema, menuCompanyAssignmentsSchema,
|
||||
menuPreviewSchema, menuAuditLogQuerySchema, menuPlanParamSchema, menuCompanyParamSchema,
|
||||
} from './admin.schemas'
|
||||
import { z } from 'zod'
|
||||
|
||||
@@ -127,6 +130,92 @@ router.post('/companies/:id/impersonate', requireAdminAuth, requireAdminRole('AD
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
// ─── Menu management ──────────────────────────────────────────
|
||||
|
||||
router.get('/menu-items', requireAdminAuth, requireAdminRole('SUPPORT'), async (_req, res, next) => {
|
||||
try {
|
||||
ok(res, await menuService.listMenuItems())
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/menu-items', requireAdminAuth, requireAdminRole('ADMIN'), async (req, res, next) => {
|
||||
try {
|
||||
created(res, { data: await menuService.createMenuItem(parseBody(menuItemSchema, req), req.admin) })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.get('/menu-items/:id', requireAdminAuth, requireAdminRole('SUPPORT'), async (req, res, next) => {
|
||||
try {
|
||||
const { id } = parseParams(idParamSchema, req)
|
||||
ok(res, await menuService.getMenuItem(id))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.put('/menu-items/:id', requireAdminAuth, requireAdminRole('ADMIN'), async (req, res, next) => {
|
||||
try {
|
||||
const { id } = parseParams(idParamSchema, req)
|
||||
ok(res, await menuService.updateMenuItem(id, parseBody(menuItemSchema, req), req.admin))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.patch('/menu-items/:id/status', requireAdminAuth, requireAdminRole('ADMIN'), async (req, res, next) => {
|
||||
try {
|
||||
const { id } = parseParams(idParamSchema, req)
|
||||
const { isActive } = parseBody(menuItemStatusSchema, req)
|
||||
ok(res, await menuService.setMenuItemStatus(id, isActive, req.admin))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.delete('/menu-items/:id', requireAdminAuth, requireAdminRole('ADMIN'), async (req, res, next) => {
|
||||
try {
|
||||
const { id } = parseParams(idParamSchema, req)
|
||||
await menuService.deleteMenuItem(id, req.admin)
|
||||
ok(res, { success: true })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/menu-items/:id/subscriptions', requireAdminAuth, requireAdminRole('ADMIN'), async (req, res, next) => {
|
||||
try {
|
||||
const { id } = parseParams(idParamSchema, req)
|
||||
const { assignments } = parseBody(menuPlanAssignmentsSchema, req)
|
||||
ok(res, await menuService.assignMenuItemToPlans(id, assignments, req.admin))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.delete('/menu-items/:id/subscriptions/:plan', requireAdminAuth, requireAdminRole('ADMIN'), async (req, res, next) => {
|
||||
try {
|
||||
const { id, plan } = parseParams(menuPlanParamSchema, req)
|
||||
ok(res, await menuService.removeMenuItemFromPlan(id, plan, req.admin))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/menu-items/:id/companies', requireAdminAuth, requireAdminRole('ADMIN'), async (req, res, next) => {
|
||||
try {
|
||||
const { id } = parseParams(idParamSchema, req)
|
||||
const { assignments } = parseBody(menuCompanyAssignmentsSchema, req)
|
||||
ok(res, await menuService.assignMenuItemToCompanies(id, assignments, req.admin))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.delete('/menu-items/:id/companies/:companyId', requireAdminAuth, requireAdminRole('ADMIN'), async (req, res, next) => {
|
||||
try {
|
||||
const { id, companyId } = parseParams(menuCompanyParamSchema, req)
|
||||
ok(res, await menuService.removeMenuItemFromCompany(id, companyId, req.admin))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/menu-preview', requireAdminAuth, requireAdminRole('SUPPORT'), async (req, res, next) => {
|
||||
try {
|
||||
ok(res, await menuService.previewCompanyMenu(parseBody(menuPreviewSchema, req)))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.get('/menu-audit-logs', requireAdminAuth, requireAdminRole('SUPPORT'), async (req, res, next) => {
|
||||
try {
|
||||
ok(res, await menuService.listMenuAuditLogs(parseQuery(menuAuditLogQuerySchema, req)))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
// ─── Renters ───────────────────────────────────────────────────
|
||||
|
||||
router.get('/renters', requireAdminAuth, async (req, res, next) => {
|
||||
|
||||
@@ -332,3 +332,75 @@ export const promotionUpdateSchema = promotionCreateSchema.partial()
|
||||
|
||||
export const planFeatureIdParamSchema = z.object({ featureId: z.string().min(1) })
|
||||
export const promotionIdParamSchema = z.object({ promotionId: z.string().min(1) })
|
||||
|
||||
const employeeRoleSchema = z.enum(['OWNER', 'MANAGER', 'AGENT'])
|
||||
const planSchema = z.enum(['STARTER', 'GROWTH', 'PRO'])
|
||||
const menuItemTypeSchema = z.enum(['INTERNAL_PAGE', 'EXTERNAL_LINK', 'PARENT_MENU', 'SECTION_LABEL', 'DIVIDER'])
|
||||
|
||||
export const menuItemSchema = z.object({
|
||||
systemKey: z.string().trim().min(1).max(120).optional().nullable(),
|
||||
label: z.string().trim().min(1).max(120),
|
||||
itemType: menuItemTypeSchema,
|
||||
routeOrUrl: z.string().trim().max(400).optional().nullable(),
|
||||
icon: z.string().trim().max(120).optional().nullable(),
|
||||
parentId: z.string().trim().min(1).optional().nullable(),
|
||||
displayOrder: z.number().int().min(0).default(0),
|
||||
openInNewTab: z.boolean().default(false),
|
||||
isRequired: z.boolean().default(false),
|
||||
isActive: z.boolean().default(true),
|
||||
roles: z.array(employeeRoleSchema).default([]),
|
||||
subscriptionPlans: z.array(z.object({
|
||||
plan: planSchema,
|
||||
displayOrder: z.number().int().min(0).optional(),
|
||||
isActive: z.boolean().optional(),
|
||||
})).default([]),
|
||||
companyAssignments: z.array(z.object({
|
||||
companyId: z.string().trim().min(1),
|
||||
displayOrder: z.number().int().min(0).optional(),
|
||||
isActive: z.boolean().optional(),
|
||||
})).default([]),
|
||||
})
|
||||
|
||||
export const menuItemStatusSchema = z.object({
|
||||
isActive: z.boolean(),
|
||||
})
|
||||
|
||||
export const menuPlanAssignmentsSchema = z.object({
|
||||
assignments: z.array(z.object({
|
||||
plan: planSchema,
|
||||
displayOrder: z.number().int().min(0).optional(),
|
||||
isActive: z.boolean().optional(),
|
||||
})).min(1),
|
||||
})
|
||||
|
||||
export const menuCompanyAssignmentsSchema = z.object({
|
||||
assignments: z.array(z.object({
|
||||
companyId: z.string().trim().min(1),
|
||||
displayOrder: z.number().int().min(0).optional(),
|
||||
isActive: z.boolean().optional(),
|
||||
})).min(1),
|
||||
})
|
||||
|
||||
export const menuPreviewSchema = z.object({
|
||||
companyId: z.string().trim().min(1),
|
||||
role: employeeRoleSchema,
|
||||
})
|
||||
|
||||
export const menuAuditLogQuerySchema = z.object({
|
||||
page: z.coerce.number().int().min(1).default(1),
|
||||
pageSize: z.coerce.number().int().min(1).max(100).default(20),
|
||||
})
|
||||
|
||||
export const menuItemIdParamSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
})
|
||||
|
||||
export const menuPlanParamSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
plan: planSchema,
|
||||
})
|
||||
|
||||
export const menuCompanyParamSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
companyId: z.string().min(1),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user