@actuate-media/cms-core 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/api/handlers.d.ts.map +1 -1
  2. package/dist/api/handlers.js +530 -0
  3. package/dist/api/handlers.js.map +1 -1
  4. package/dist/config/types.d.ts +3 -0
  5. package/dist/config/types.d.ts.map +1 -1
  6. package/dist/config/types.js +0 -1
  7. package/dist/config/types.js.map +1 -1
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +2 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/page-builder/__tests__/a11y-fix.test.d.ts +2 -0
  13. package/dist/page-builder/__tests__/a11y-fix.test.d.ts.map +1 -0
  14. package/dist/page-builder/__tests__/a11y-fix.test.js +246 -0
  15. package/dist/page-builder/__tests__/a11y-fix.test.js.map +1 -0
  16. package/dist/page-builder/__tests__/blocks.test.d.ts +2 -0
  17. package/dist/page-builder/__tests__/blocks.test.d.ts.map +1 -0
  18. package/dist/page-builder/__tests__/blocks.test.js +87 -0
  19. package/dist/page-builder/__tests__/blocks.test.js.map +1 -0
  20. package/dist/page-builder/__tests__/design-scorer.test.d.ts +2 -0
  21. package/dist/page-builder/__tests__/design-scorer.test.d.ts.map +1 -0
  22. package/dist/page-builder/__tests__/design-scorer.test.js +268 -0
  23. package/dist/page-builder/__tests__/design-scorer.test.js.map +1 -0
  24. package/dist/page-builder/__tests__/schema.test.d.ts +2 -0
  25. package/dist/page-builder/__tests__/schema.test.d.ts.map +1 -0
  26. package/dist/page-builder/__tests__/schema.test.js +191 -0
  27. package/dist/page-builder/__tests__/schema.test.js.map +1 -0
  28. package/dist/page-builder/__tests__/seo-analyzer.test.d.ts +2 -0
  29. package/dist/page-builder/__tests__/seo-analyzer.test.d.ts.map +1 -0
  30. package/dist/page-builder/__tests__/seo-analyzer.test.js +332 -0
  31. package/dist/page-builder/__tests__/seo-analyzer.test.js.map +1 -0
  32. package/dist/page-builder/__tests__/tree.test.d.ts +2 -0
  33. package/dist/page-builder/__tests__/tree.test.d.ts.map +1 -0
  34. package/dist/page-builder/__tests__/tree.test.js +257 -0
  35. package/dist/page-builder/__tests__/tree.test.js.map +1 -0
  36. package/dist/page-builder/a11y-fix.d.ts +20 -0
  37. package/dist/page-builder/a11y-fix.d.ts.map +1 -0
  38. package/dist/page-builder/a11y-fix.js +182 -0
  39. package/dist/page-builder/a11y-fix.js.map +1 -0
  40. package/dist/page-builder/ai-pipeline.d.ts +43 -0
  41. package/dist/page-builder/ai-pipeline.d.ts.map +1 -0
  42. package/dist/page-builder/ai-pipeline.js +167 -0
  43. package/dist/page-builder/ai-pipeline.js.map +1 -0
  44. package/dist/page-builder/blocks.d.ts +10 -0
  45. package/dist/page-builder/blocks.d.ts.map +1 -0
  46. package/dist/page-builder/blocks.js +252 -0
  47. package/dist/page-builder/blocks.js.map +1 -0
  48. package/dist/page-builder/design-scorer.d.ts +27 -0
  49. package/dist/page-builder/design-scorer.d.ts.map +1 -0
  50. package/dist/page-builder/design-scorer.js +404 -0
  51. package/dist/page-builder/design-scorer.js.map +1 -0
  52. package/dist/page-builder/index.d.ts +18 -0
  53. package/dist/page-builder/index.d.ts.map +1 -0
  54. package/dist/page-builder/index.js +20 -0
  55. package/dist/page-builder/index.js.map +1 -0
  56. package/dist/page-builder/schema.d.ts +107 -0
  57. package/dist/page-builder/schema.d.ts.map +1 -0
  58. package/dist/page-builder/schema.js +90 -0
  59. package/dist/page-builder/schema.js.map +1 -0
  60. package/dist/page-builder/seo-analyzer.d.ts +61 -0
  61. package/dist/page-builder/seo-analyzer.d.ts.map +1 -0
  62. package/dist/page-builder/seo-analyzer.js +582 -0
  63. package/dist/page-builder/seo-analyzer.js.map +1 -0
  64. package/dist/page-builder/templates.d.ts +9 -0
  65. package/dist/page-builder/templates.d.ts.map +1 -0
  66. package/dist/page-builder/templates.js +401 -0
  67. package/dist/page-builder/templates.js.map +1 -0
  68. package/dist/page-builder/tree.d.ts +17 -0
  69. package/dist/page-builder/tree.d.ts.map +1 -0
  70. package/dist/page-builder/tree.js +160 -0
  71. package/dist/page-builder/tree.js.map +1 -0
  72. package/dist/page-builder/types.d.ts +112 -0
  73. package/dist/page-builder/types.d.ts.map +1 -0
  74. package/dist/page-builder/types.js +4 -0
  75. package/dist/page-builder/types.js.map +1 -0
  76. package/dist/page-builder/validate.d.ts +6 -0
  77. package/dist/page-builder/validate.d.ts.map +1 -0
  78. package/dist/page-builder/validate.js +87 -0
  79. package/dist/page-builder/validate.js.map +1 -0
  80. package/package.json +6 -1
  81. package/prisma/migrations/0006_page_builder/migration.sql +38 -0
  82. package/prisma/schema.prisma +31 -0
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/api/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAmN7C,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS9E;AAiED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAm9FzD"}
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/api/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AA4N7C,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS9E;AAiED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAggHzD"}
@@ -18,12 +18,19 @@ import { encryptField, decryptField } from '../security/encrypted-fields.js';
18
18
  import { createRateLimiter } from '../security/rate-limit.js';
19
19
  import { generateOpenAPISpec } from './openapi.js';
20
20
  import { createSSEPresenceAdapter } from '../presence/index.js';
21
+ import { BUILT_IN_TEMPLATES } from '../page-builder/templates.js';
22
+ import { validateTree } from '../page-builder/validate.js';
23
+ import { auditAccessibility, fixAccessibility } from '../page-builder/a11y-fix.js';
21
24
  // Opaque dynamic import so Turbopack/webpack won't statically analyze the specifier.
22
25
  // Returns { put, del, ... } from @vercel/blob when available.
23
26
  async function importBlobStorage() {
24
27
  const mod = '@vercel/' + 'blob';
25
28
  return import(/* webpackIgnore: true */ mod);
26
29
  }
30
+ async function importAIPlugin() {
31
+ const mod = '@actuate-media/' + 'plugin-ai';
32
+ return import(/* webpackIgnore: true */ mod);
33
+ }
27
34
  const SECURITY_HEADERS = {
28
35
  'Content-Type': 'application/json',
29
36
  'X-Content-Type-Options': 'nosniff',
@@ -3055,5 +3062,528 @@ export function registerCMSRoutes(router) {
3055
3062
  return internalError(err);
3056
3063
  }
3057
3064
  });
3065
+ // ---------------------------------------------------------------------------
3066
+ // Page Templates
3067
+ // ---------------------------------------------------------------------------
3068
+ async function seedBuiltInTemplates(d) {
3069
+ for (const [key, template] of Object.entries(BUILT_IN_TEMPLATES)) {
3070
+ await d.pageTemplate.upsert({
3071
+ where: { id: `builtin-${key}` },
3072
+ create: {
3073
+ id: `builtin-${key}`,
3074
+ name: template.name,
3075
+ description: template.description,
3076
+ category: template.category,
3077
+ tree: template.tree,
3078
+ builtIn: true,
3079
+ },
3080
+ update: {
3081
+ name: template.name,
3082
+ description: template.description,
3083
+ category: template.category,
3084
+ tree: template.tree,
3085
+ },
3086
+ });
3087
+ }
3088
+ }
3089
+ router.get('/page-templates', async (request) => {
3090
+ try {
3091
+ const auth = await requireAuth(request);
3092
+ if (auth.error)
3093
+ return auth.error;
3094
+ const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
3095
+ if (roleErr)
3096
+ return roleErr;
3097
+ const d = db();
3098
+ if (!hasModel(d, 'pageTemplate'))
3099
+ return modelNotAvailable('PageTemplate');
3100
+ const url = new URL(request.url, 'http://localhost');
3101
+ const category = url.searchParams.get('category');
3102
+ const builtInCount = await safeCount(d.pageTemplate, { builtIn: true });
3103
+ if (builtInCount === 0) {
3104
+ await seedBuiltInTemplates(d);
3105
+ }
3106
+ const where = {};
3107
+ if (category)
3108
+ where.category = category;
3109
+ const templates = await d.pageTemplate.findMany({
3110
+ where,
3111
+ orderBy: [{ builtIn: 'desc' }, { updatedAt: 'desc' }],
3112
+ });
3113
+ return json({ data: templates });
3114
+ }
3115
+ catch (err) {
3116
+ return internalError(err, 'page-templates list');
3117
+ }
3118
+ });
3119
+ router.get('/page-templates/:id', async (request, params) => {
3120
+ try {
3121
+ const auth = await requireAuth(request);
3122
+ if (auth.error)
3123
+ return auth.error;
3124
+ const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
3125
+ if (roleErr)
3126
+ return roleErr;
3127
+ const d = db();
3128
+ if (!hasModel(d, 'pageTemplate'))
3129
+ return modelNotAvailable('PageTemplate');
3130
+ const template = await d.pageTemplate.findUnique({ where: { id: params.id } });
3131
+ if (!template)
3132
+ return errorResponse('Template not found', 404);
3133
+ return json({ data: template });
3134
+ }
3135
+ catch (err) {
3136
+ return internalError(err, 'page-templates get');
3137
+ }
3138
+ });
3139
+ router.post('/page-templates', async (request) => {
3140
+ try {
3141
+ const auth = await requireAuth(request);
3142
+ if (auth.error)
3143
+ return auth.error;
3144
+ const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
3145
+ if (roleErr)
3146
+ return roleErr;
3147
+ const d = db();
3148
+ if (!hasModel(d, 'pageTemplate'))
3149
+ return modelNotAvailable('PageTemplate');
3150
+ const body = await request.json();
3151
+ if (!body.name)
3152
+ return errorResponse('name is required', 400);
3153
+ if (!body.tree)
3154
+ return errorResponse('tree is required', 400);
3155
+ const validation = validateTree(body.tree);
3156
+ if (!validation.valid) {
3157
+ return errorResponse(`Invalid tree: ${validation.errors.join('; ')}`, 400);
3158
+ }
3159
+ const validCategories = ['landing', 'content', 'utility'];
3160
+ const category = body.category || 'content';
3161
+ if (!validCategories.includes(category)) {
3162
+ return errorResponse('category must be landing, content, or utility', 400);
3163
+ }
3164
+ const template = await d.pageTemplate.create({
3165
+ data: {
3166
+ name: body.name,
3167
+ description: body.description || null,
3168
+ category,
3169
+ tree: body.tree,
3170
+ thumbnail: body.thumbnail || null,
3171
+ builtIn: false,
3172
+ },
3173
+ });
3174
+ await logEvent({
3175
+ event: 'settings_changed',
3176
+ userId: auth.session.userId,
3177
+ details: { action: 'page_template_created', templateId: template.id, name: template.name },
3178
+ });
3179
+ return json({ data: template }, 201);
3180
+ }
3181
+ catch (err) {
3182
+ return internalError(err, 'page-templates create');
3183
+ }
3184
+ });
3185
+ router.put('/page-templates/:id', async (request, params) => {
3186
+ try {
3187
+ const auth = await requireAuth(request);
3188
+ if (auth.error)
3189
+ return auth.error;
3190
+ const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
3191
+ if (roleErr)
3192
+ return roleErr;
3193
+ const d = db();
3194
+ if (!hasModel(d, 'pageTemplate'))
3195
+ return modelNotAvailable('PageTemplate');
3196
+ const existing = await d.pageTemplate.findUnique({ where: { id: params.id } });
3197
+ if (!existing)
3198
+ return errorResponse('Template not found', 404);
3199
+ if (existing.builtIn)
3200
+ return errorResponse('Cannot update built-in templates', 403);
3201
+ const body = await request.json();
3202
+ const update = {};
3203
+ if (body.name !== undefined)
3204
+ update.name = body.name;
3205
+ if (body.description !== undefined)
3206
+ update.description = body.description;
3207
+ if (body.thumbnail !== undefined)
3208
+ update.thumbnail = body.thumbnail;
3209
+ if (body.category !== undefined) {
3210
+ const validCategories = ['landing', 'content', 'utility'];
3211
+ if (!validCategories.includes(body.category)) {
3212
+ return errorResponse('category must be landing, content, or utility', 400);
3213
+ }
3214
+ update.category = body.category;
3215
+ }
3216
+ if (body.tree !== undefined) {
3217
+ const validation = validateTree(body.tree);
3218
+ if (!validation.valid) {
3219
+ return errorResponse(`Invalid tree: ${validation.errors.join('; ')}`, 400);
3220
+ }
3221
+ update.tree = body.tree;
3222
+ }
3223
+ const template = await d.pageTemplate.update({
3224
+ where: { id: params.id },
3225
+ data: update,
3226
+ });
3227
+ await logEvent({
3228
+ event: 'settings_changed',
3229
+ userId: auth.session.userId,
3230
+ details: { action: 'page_template_updated', templateId: template.id, name: template.name },
3231
+ });
3232
+ return json({ data: template });
3233
+ }
3234
+ catch (err) {
3235
+ return internalError(err, 'page-templates update');
3236
+ }
3237
+ });
3238
+ router.delete('/page-templates/:id', async (request, params) => {
3239
+ try {
3240
+ const auth = await requireAuth(request);
3241
+ if (auth.error)
3242
+ return auth.error;
3243
+ const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
3244
+ if (roleErr)
3245
+ return roleErr;
3246
+ const d = db();
3247
+ if (!hasModel(d, 'pageTemplate'))
3248
+ return modelNotAvailable('PageTemplate');
3249
+ const existing = await d.pageTemplate.findUnique({ where: { id: params.id } });
3250
+ if (!existing)
3251
+ return errorResponse('Template not found', 404);
3252
+ if (existing.builtIn)
3253
+ return errorResponse('Cannot delete built-in templates', 403);
3254
+ await d.pageTemplate.delete({ where: { id: params.id } });
3255
+ await logEvent({
3256
+ event: 'settings_changed',
3257
+ userId: auth.session.userId,
3258
+ details: { action: 'page_template_deleted', templateId: existing.id, name: existing.name },
3259
+ });
3260
+ return json({ data: { deleted: true } });
3261
+ }
3262
+ catch (err) {
3263
+ return internalError(err, 'page-templates delete');
3264
+ }
3265
+ });
3266
+ router.post('/page-templates/seed', async (request) => {
3267
+ try {
3268
+ const auth = await requireAuth(request);
3269
+ if (auth.error)
3270
+ return auth.error;
3271
+ const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
3272
+ if (roleErr)
3273
+ return roleErr;
3274
+ const d = db();
3275
+ if (!hasModel(d, 'pageTemplate'))
3276
+ return modelNotAvailable('PageTemplate');
3277
+ await seedBuiltInTemplates(d);
3278
+ await logEvent({
3279
+ event: 'settings_changed',
3280
+ userId: auth.session.userId,
3281
+ details: { action: 'page_templates_seeded' },
3282
+ });
3283
+ return json({ data: { seeded: Object.keys(BUILT_IN_TEMPLATES).length } });
3284
+ }
3285
+ catch (err) {
3286
+ return internalError(err, 'page-templates seed');
3287
+ }
3288
+ });
3289
+ // ---------------------------------------------------------------------------
3290
+ // Saved Sections
3291
+ // ---------------------------------------------------------------------------
3292
+ router.get('/saved-sections', async (request) => {
3293
+ try {
3294
+ const auth = await requireAuth(request);
3295
+ if (auth.error)
3296
+ return auth.error;
3297
+ const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
3298
+ if (roleErr)
3299
+ return roleErr;
3300
+ const d = db();
3301
+ if (!hasModel(d, 'savedSection'))
3302
+ return modelNotAvailable('SavedSection');
3303
+ const url = new URL(request.url, 'http://localhost');
3304
+ const category = url.searchParams.get('category');
3305
+ const where = {};
3306
+ if (category)
3307
+ where.category = category;
3308
+ const sections = await d.savedSection.findMany({
3309
+ where,
3310
+ orderBy: { updatedAt: 'desc' },
3311
+ });
3312
+ return json({ data: sections });
3313
+ }
3314
+ catch (err) {
3315
+ return internalError(err, 'saved-sections list');
3316
+ }
3317
+ });
3318
+ router.get('/saved-sections/:id', async (request, params) => {
3319
+ try {
3320
+ const auth = await requireAuth(request);
3321
+ if (auth.error)
3322
+ return auth.error;
3323
+ const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
3324
+ if (roleErr)
3325
+ return roleErr;
3326
+ const d = db();
3327
+ if (!hasModel(d, 'savedSection'))
3328
+ return modelNotAvailable('SavedSection');
3329
+ const section = await d.savedSection.findUnique({ where: { id: params.id } });
3330
+ if (!section)
3331
+ return errorResponse('Saved section not found', 404);
3332
+ return json({ data: section });
3333
+ }
3334
+ catch (err) {
3335
+ return internalError(err, 'saved-sections get');
3336
+ }
3337
+ });
3338
+ router.post('/saved-sections', async (request) => {
3339
+ try {
3340
+ const auth = await requireAuth(request);
3341
+ if (auth.error)
3342
+ return auth.error;
3343
+ const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
3344
+ if (roleErr)
3345
+ return roleErr;
3346
+ const d = db();
3347
+ if (!hasModel(d, 'savedSection'))
3348
+ return modelNotAvailable('SavedSection');
3349
+ const body = await request.json();
3350
+ if (!body.name)
3351
+ return errorResponse('name is required', 400);
3352
+ if (!body.tree)
3353
+ return errorResponse('tree is required', 400);
3354
+ const wrappedTree = { id: '__validation_wrapper__', type: 'page', children: [body.tree] };
3355
+ const validation = validateTree(wrappedTree);
3356
+ if (!validation.valid) {
3357
+ return errorResponse(`Invalid section tree: ${validation.errors.join('; ')}`, 400);
3358
+ }
3359
+ const validCategories = ['header', 'footer', 'content', 'sidebar'];
3360
+ const category = body.category || 'content';
3361
+ if (!validCategories.includes(category)) {
3362
+ return errorResponse('category must be header, footer, content, or sidebar', 400);
3363
+ }
3364
+ const section = await d.savedSection.create({
3365
+ data: {
3366
+ name: body.name,
3367
+ description: body.description || null,
3368
+ category,
3369
+ tree: body.tree,
3370
+ thumbnail: body.thumbnail || null,
3371
+ },
3372
+ });
3373
+ await logEvent({
3374
+ event: 'settings_changed',
3375
+ userId: auth.session.userId,
3376
+ details: { action: 'saved_section_created', sectionId: section.id, name: section.name },
3377
+ });
3378
+ return json({ data: section }, 201);
3379
+ }
3380
+ catch (err) {
3381
+ return internalError(err, 'saved-sections create');
3382
+ }
3383
+ });
3384
+ router.put('/saved-sections/:id', async (request, params) => {
3385
+ try {
3386
+ const auth = await requireAuth(request);
3387
+ if (auth.error)
3388
+ return auth.error;
3389
+ const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
3390
+ if (roleErr)
3391
+ return roleErr;
3392
+ const d = db();
3393
+ if (!hasModel(d, 'savedSection'))
3394
+ return modelNotAvailable('SavedSection');
3395
+ const existing = await d.savedSection.findUnique({ where: { id: params.id } });
3396
+ if (!existing)
3397
+ return errorResponse('Saved section not found', 404);
3398
+ const body = await request.json();
3399
+ const update = {};
3400
+ if (body.name !== undefined)
3401
+ update.name = body.name;
3402
+ if (body.description !== undefined)
3403
+ update.description = body.description;
3404
+ if (body.thumbnail !== undefined)
3405
+ update.thumbnail = body.thumbnail;
3406
+ if (body.category !== undefined) {
3407
+ const validCategories = ['header', 'footer', 'content', 'sidebar'];
3408
+ if (!validCategories.includes(body.category)) {
3409
+ return errorResponse('category must be header, footer, content, or sidebar', 400);
3410
+ }
3411
+ update.category = body.category;
3412
+ }
3413
+ if (body.tree !== undefined) {
3414
+ const wrappedTree = { id: '__validation_wrapper__', type: 'page', children: [body.tree] };
3415
+ const validation = validateTree(wrappedTree);
3416
+ if (!validation.valid) {
3417
+ return errorResponse(`Invalid section tree: ${validation.errors.join('; ')}`, 400);
3418
+ }
3419
+ update.tree = body.tree;
3420
+ }
3421
+ const section = await d.savedSection.update({
3422
+ where: { id: params.id },
3423
+ data: update,
3424
+ });
3425
+ await logEvent({
3426
+ event: 'settings_changed',
3427
+ userId: auth.session.userId,
3428
+ details: { action: 'saved_section_updated', sectionId: section.id, name: section.name },
3429
+ });
3430
+ return json({ data: section });
3431
+ }
3432
+ catch (err) {
3433
+ return internalError(err, 'saved-sections update');
3434
+ }
3435
+ });
3436
+ router.delete('/saved-sections/:id', async (request, params) => {
3437
+ try {
3438
+ const auth = await requireAuth(request);
3439
+ if (auth.error)
3440
+ return auth.error;
3441
+ const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
3442
+ if (roleErr)
3443
+ return roleErr;
3444
+ const d = db();
3445
+ if (!hasModel(d, 'savedSection'))
3446
+ return modelNotAvailable('SavedSection');
3447
+ const existing = await d.savedSection.findUnique({ where: { id: params.id } });
3448
+ if (!existing)
3449
+ return errorResponse('Saved section not found', 404);
3450
+ await d.savedSection.delete({ where: { id: params.id } });
3451
+ await logEvent({
3452
+ event: 'settings_changed',
3453
+ userId: auth.session.userId,
3454
+ details: { action: 'saved_section_deleted', sectionId: existing.id, name: existing.name },
3455
+ });
3456
+ return json({ data: { deleted: true } });
3457
+ }
3458
+ catch (err) {
3459
+ return internalError(err, 'saved-sections delete');
3460
+ }
3461
+ });
3462
+ // ─── Page Builder AI Generation ─────────────────────────────────────
3463
+ router.post('/page-builder/generate', async (request) => {
3464
+ try {
3465
+ const auth = await requireAuth(request);
3466
+ if (auth.error)
3467
+ return auth.error;
3468
+ const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
3469
+ if (roleErr)
3470
+ return roleErr;
3471
+ const body = await request.json();
3472
+ const { prompt, template, context, steps, tone } = body;
3473
+ if (!prompt || typeof prompt !== 'string') {
3474
+ return errorResponse('prompt is required', 400);
3475
+ }
3476
+ if (!steps || !Array.isArray(steps) || steps.length === 0) {
3477
+ return errorResponse('steps array is required', 400);
3478
+ }
3479
+ const validSteps = ['structure', 'content', 'seo', 'accessibility'];
3480
+ for (const s of steps) {
3481
+ if (!validSteps.includes(s)) {
3482
+ return errorResponse(`Invalid step: ${s}. Valid steps: ${validSteps.join(', ')}`, 400);
3483
+ }
3484
+ }
3485
+ await logEvent({
3486
+ event: 'settings_changed',
3487
+ userId: auth.session.userId,
3488
+ details: { action: 'page_generation_started', prompt, steps, template },
3489
+ });
3490
+ let generatePage = null;
3491
+ try {
3492
+ const aiModule = await importAIPlugin();
3493
+ generatePage = aiModule.generatePage;
3494
+ }
3495
+ catch {
3496
+ return errorResponse('AI plugin is not installed. Install @actuate-media/plugin-ai to use page generation.', 501);
3497
+ }
3498
+ const result = await generatePage({ prompt, template, context, steps, tone });
3499
+ await logEvent({
3500
+ event: 'settings_changed',
3501
+ userId: auth.session.userId,
3502
+ details: {
3503
+ action: 'page_generation_completed',
3504
+ totalTokensUsed: result.totalTokensUsed,
3505
+ totalDurationMs: result.totalDurationMs,
3506
+ stepsCompleted: result.steps.map((s) => s.step),
3507
+ },
3508
+ });
3509
+ return json({ data: result });
3510
+ }
3511
+ catch (err) {
3512
+ return internalError(err, 'page-builder generate');
3513
+ }
3514
+ });
3515
+ router.post('/page-builder/generate-block', async (request) => {
3516
+ try {
3517
+ const auth = await requireAuth(request);
3518
+ if (auth.error)
3519
+ return auth.error;
3520
+ const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
3521
+ if (roleErr)
3522
+ return roleErr;
3523
+ const body = await request.json();
3524
+ const { blockType, variant, pageContext, tone } = body;
3525
+ if (!blockType || typeof blockType !== 'string') {
3526
+ return errorResponse('blockType is required', 400);
3527
+ }
3528
+ let generateBlockContent = null;
3529
+ try {
3530
+ const aiModule = await importAIPlugin();
3531
+ generateBlockContent = aiModule.generateBlockContent;
3532
+ }
3533
+ catch {
3534
+ return errorResponse('AI plugin is not installed. Install @actuate-media/plugin-ai to use block generation.', 501);
3535
+ }
3536
+ const data = await generateBlockContent({ blockType, variant, pageContext, tone });
3537
+ return json({ data });
3538
+ }
3539
+ catch (err) {
3540
+ return internalError(err, 'page-builder generate-block');
3541
+ }
3542
+ });
3543
+ router.post('/page-builder/audit-a11y', async (request) => {
3544
+ try {
3545
+ const auth = await requireAuth(request);
3546
+ if (auth.error)
3547
+ return auth.error;
3548
+ const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
3549
+ if (roleErr)
3550
+ return roleErr;
3551
+ const body = await request.json();
3552
+ const tree = body.tree;
3553
+ if (!tree || tree.type !== 'page') {
3554
+ return errorResponse('A valid page tree is required', 400);
3555
+ }
3556
+ const issues = auditAccessibility(tree);
3557
+ return json({ data: { issues } });
3558
+ }
3559
+ catch (err) {
3560
+ return internalError(err, 'page-builder audit-a11y');
3561
+ }
3562
+ });
3563
+ router.post('/page-builder/fix-a11y', async (request) => {
3564
+ try {
3565
+ const auth = await requireAuth(request);
3566
+ if (auth.error)
3567
+ return auth.error;
3568
+ const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
3569
+ if (roleErr)
3570
+ return roleErr;
3571
+ const body = await request.json();
3572
+ const tree = body.tree;
3573
+ if (!tree || tree.type !== 'page') {
3574
+ return errorResponse('A valid page tree is required', 400);
3575
+ }
3576
+ const result = fixAccessibility(tree);
3577
+ await logEvent({
3578
+ event: 'settings_changed',
3579
+ userId: auth.session.userId,
3580
+ details: { action: 'a11y_auto_fix', fixedCount: result.report.fixedCount, remainingCount: result.report.remainingCount },
3581
+ });
3582
+ return json({ data: result });
3583
+ }
3584
+ catch (err) {
3585
+ return internalError(err, 'page-builder fix-a11y');
3586
+ }
3587
+ });
3058
3588
  }
3059
3589
  //# sourceMappingURL=handlers.js.map