141 lines
3.6 KiB
TypeScript
141 lines
3.6 KiB
TypeScript
import { Router, Request, Response, NextFunction } from 'express';
|
|
import { pagesService } from './pages.service';
|
|
import { createLandingPageSchema, updateLandingPageSchema, listLandingPagesSchema } from './pages.schemas';
|
|
import { validate } from '../../middleware/validate';
|
|
import { authenticate } from '../../middleware/auth.middleware';
|
|
import { requireRole } from '../../middleware/rbac.middleware';
|
|
import { prisma } from '../../config/database';
|
|
import { CONTENT_ROLES } from '../../utils/roles';
|
|
|
|
const router = Router();
|
|
|
|
router.use(authenticate);
|
|
router.use(requireRole(...CONTENT_ROLES));
|
|
|
|
// GET /api/pages/view-counts — landing page view counts (last 30d)
|
|
router.get(
|
|
'/view-counts',
|
|
async (_req: Request, res: Response, next: NextFunction) => {
|
|
try {
|
|
const since = new Date();
|
|
since.setDate(since.getDate() - 30);
|
|
const rows = await prisma.docsPageView.groupBy({
|
|
by: ['path'],
|
|
where: {
|
|
path: { startsWith: '/p/' },
|
|
createdAt: { gte: since },
|
|
},
|
|
_count: { id: true },
|
|
});
|
|
const counts: Record<string, number> = {};
|
|
for (const row of rows) {
|
|
// Extract slug from /p/:slug
|
|
const slug = row.path.replace(/^\/p\//, '');
|
|
counts[slug] = row._count.id;
|
|
}
|
|
res.json(counts);
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
}
|
|
);
|
|
|
|
// POST /api/pages/sync — sync MkDocs overrides (must be before /:id routes)
|
|
router.post(
|
|
'/sync',
|
|
async (_req: Request, res: Response, next: NextFunction) => {
|
|
try {
|
|
const result = await pagesService.syncOverrides();
|
|
res.json(result);
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
}
|
|
);
|
|
|
|
// POST /api/pages/validate — validate and repair MkDocs exports
|
|
router.post(
|
|
'/validate',
|
|
async (_req: Request, res: Response, next: NextFunction) => {
|
|
try {
|
|
const result = await pagesService.validateExports();
|
|
res.json(result);
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
}
|
|
);
|
|
|
|
// GET /api/pages — list landing pages with pagination/filters
|
|
router.get(
|
|
'/',
|
|
validate(listLandingPagesSchema, 'query'),
|
|
async (req: Request, res: Response, next: NextFunction) => {
|
|
try {
|
|
const result = await pagesService.findAll(req.query as any);
|
|
res.json(result);
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
}
|
|
);
|
|
|
|
// GET /api/pages/:id — get single landing page
|
|
router.get(
|
|
'/:id',
|
|
async (req: Request, res: Response, next: NextFunction) => {
|
|
try {
|
|
const id = req.params.id as string;
|
|
const page = await pagesService.findById(id);
|
|
res.json(page);
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
}
|
|
);
|
|
|
|
// POST /api/pages — create landing page
|
|
router.post(
|
|
'/',
|
|
validate(createLandingPageSchema),
|
|
async (req: Request, res: Response, next: NextFunction) => {
|
|
try {
|
|
const page = await pagesService.create(req.body);
|
|
res.status(201).json(page);
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
}
|
|
);
|
|
|
|
// PUT /api/pages/:id — update landing page
|
|
router.put(
|
|
'/:id',
|
|
validate(updateLandingPageSchema),
|
|
async (req: Request, res: Response, next: NextFunction) => {
|
|
try {
|
|
const id = req.params.id as string;
|
|
const page = await pagesService.update(id, req.body);
|
|
res.json(page);
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
}
|
|
);
|
|
|
|
// DELETE /api/pages/:id — delete landing page
|
|
router.delete(
|
|
'/:id',
|
|
async (req: Request, res: Response, next: NextFunction) => {
|
|
try {
|
|
const id = req.params.id as string;
|
|
await pagesService.delete(id);
|
|
res.status(204).send();
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
}
|
|
);
|
|
|
|
export { router as pagesAdminRouter };
|