@actuate-media/cms-core 0.28.1 → 0.29.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 (81) hide show
  1. package/dist/__tests__/media/seo.test.d.ts +2 -0
  2. package/dist/__tests__/media/seo.test.d.ts.map +1 -0
  3. package/dist/__tests__/media/seo.test.js +186 -0
  4. package/dist/__tests__/media/seo.test.js.map +1 -0
  5. package/dist/__tests__/redirects/graph.test.d.ts +2 -0
  6. package/dist/__tests__/redirects/graph.test.d.ts.map +1 -0
  7. package/dist/__tests__/redirects/graph.test.js +104 -0
  8. package/dist/__tests__/redirects/graph.test.js.map +1 -0
  9. package/dist/__tests__/redirects/suggest.test.d.ts +2 -0
  10. package/dist/__tests__/redirects/suggest.test.d.ts.map +1 -0
  11. package/dist/__tests__/redirects/suggest.test.js +28 -0
  12. package/dist/__tests__/redirects/suggest.test.js.map +1 -0
  13. package/dist/__tests__/seo/audit-engine.test.d.ts +2 -0
  14. package/dist/__tests__/seo/audit-engine.test.d.ts.map +1 -0
  15. package/dist/__tests__/seo/audit-engine.test.js +152 -0
  16. package/dist/__tests__/seo/audit-engine.test.js.map +1 -0
  17. package/dist/__tests__/seo/config-store.test.js +24 -160
  18. package/dist/__tests__/seo/config-store.test.js.map +1 -1
  19. package/dist/__tests__/seo/score.test.d.ts +2 -0
  20. package/dist/__tests__/seo/score.test.d.ts.map +1 -0
  21. package/dist/__tests__/seo/score.test.js +152 -0
  22. package/dist/__tests__/seo/score.test.js.map +1 -0
  23. package/dist/__tests__/setup/index.test.d.ts +2 -0
  24. package/dist/__tests__/setup/index.test.d.ts.map +1 -0
  25. package/dist/__tests__/setup/index.test.js +82 -0
  26. package/dist/__tests__/setup/index.test.js.map +1 -0
  27. package/dist/api/handlers.d.ts.map +1 -1
  28. package/dist/api/handlers.js +2095 -228
  29. package/dist/api/handlers.js.map +1 -1
  30. package/dist/index.d.ts +4 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +4 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/media/index.d.ts +2 -0
  35. package/dist/media/index.d.ts.map +1 -1
  36. package/dist/media/index.js +1 -0
  37. package/dist/media/index.js.map +1 -1
  38. package/dist/media/seo.d.ts +88 -0
  39. package/dist/media/seo.d.ts.map +1 -0
  40. package/dist/media/seo.js +215 -0
  41. package/dist/media/seo.js.map +1 -0
  42. package/dist/redirects/graph.d.ts +68 -0
  43. package/dist/redirects/graph.d.ts.map +1 -0
  44. package/dist/redirects/graph.js +136 -0
  45. package/dist/redirects/graph.js.map +1 -0
  46. package/dist/redirects/index.d.ts +5 -0
  47. package/dist/redirects/index.d.ts.map +1 -0
  48. package/dist/redirects/index.js +3 -0
  49. package/dist/redirects/index.js.map +1 -0
  50. package/dist/redirects/suggest.d.ts +21 -0
  51. package/dist/redirects/suggest.d.ts.map +1 -0
  52. package/dist/redirects/suggest.js +55 -0
  53. package/dist/redirects/suggest.js.map +1 -0
  54. package/dist/seo/audit-engine.d.ts +119 -0
  55. package/dist/seo/audit-engine.d.ts.map +1 -0
  56. package/dist/seo/audit-engine.js +402 -0
  57. package/dist/seo/audit-engine.js.map +1 -0
  58. package/dist/seo/audit-runner.d.ts +41 -0
  59. package/dist/seo/audit-runner.d.ts.map +1 -0
  60. package/dist/seo/audit-runner.js +223 -0
  61. package/dist/seo/audit-runner.js.map +1 -0
  62. package/dist/seo/config-store.d.ts +28 -0
  63. package/dist/seo/config-store.d.ts.map +1 -1
  64. package/dist/seo/config-store.js +22 -0
  65. package/dist/seo/config-store.js.map +1 -1
  66. package/dist/seo/index.d.ts +7 -2
  67. package/dist/seo/index.d.ts.map +1 -1
  68. package/dist/seo/index.js +4 -1
  69. package/dist/seo/index.js.map +1 -1
  70. package/dist/seo/score.d.ts +128 -0
  71. package/dist/seo/score.d.ts.map +1 -0
  72. package/dist/seo/score.js +213 -0
  73. package/dist/seo/score.js.map +1 -0
  74. package/dist/setup/index.d.ts +16 -1
  75. package/dist/setup/index.d.ts.map +1 -1
  76. package/dist/setup/index.js +52 -2
  77. package/dist/setup/index.js.map +1 -1
  78. package/package.json +1 -1
  79. package/prisma/migrations/0008_media_center/migration.sql +47 -0
  80. package/prisma/migrations/0009_seo/migration.sql +143 -0
  81. package/prisma/schema.prisma +183 -8
@@ -330,6 +330,12 @@ model Version {
330
330
  @@map("actuate_versions")
331
331
  }
332
332
 
333
+ enum MediaStatus {
334
+ PROCESSING
335
+ READY
336
+ FAILED
337
+ }
338
+
333
339
  model Media {
334
340
  id String @id @default(cuid())
335
341
  filename String
@@ -351,11 +357,34 @@ model Media {
351
357
  createdAt DateTime @default(now())
352
358
  updatedAt DateTime @updatedAt
353
359
 
360
+ // ── Media Center additions ──────────────────────────────────────
361
+ // All nullable / defaulted so they're safe to add to a populated DB
362
+ // with no backfill. SEO score/issues are intentionally NOT stored —
363
+ // they're derived on read by `calculateMediaSeoScore()` so they can
364
+ // never drift from the metadata below.
365
+ originalFileName String?
366
+ caption String?
367
+ description String?
368
+ focusKeyword String?
369
+ tags Json?
370
+ // `storageKey` historically held the public URL; `publicUrl` splits
371
+ // that concern out. New uploads populate both; legacy rows keep the
372
+ // URL in `storageKey` and `mediaUrl()` still resolves it.
373
+ publicUrl String?
374
+ storageProvider String?
375
+ durationSeconds Float?
376
+ thumbnailUrl String?
377
+ responsiveVariants Json?
378
+ status MediaStatus @default(READY)
379
+ deletedAt DateTime?
380
+
354
381
  uploadedBy User @relation(fields: [uploadedById], references: [id])
355
382
  folder Folder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
356
383
  mediaUsages MediaUsage[]
357
384
 
358
385
  @@index([folderId])
386
+ @@index([deletedAt])
387
+ @@index([mimeType])
359
388
  @@map("actuate_media")
360
389
  }
361
390
 
@@ -444,19 +473,165 @@ model WorkflowState {
444
473
  }
445
474
 
446
475
  model Redirect {
447
- id String @id @default(cuid())
448
- source String
449
- destination String
450
- statusCode Int @default(301)
451
- isRegex Boolean @default(false)
452
- notes String?
453
- createdAt DateTime @default(now())
454
- updatedAt DateTime @updatedAt
476
+ id String @id @default(cuid())
477
+ source String
478
+ destination String
479
+ statusCode Int @default(301)
480
+ isRegex Boolean @default(false)
481
+ notes String?
482
+ // Runtime + management metadata (added in 0009_seo). All defaulted so the
483
+ // migration is safe to apply to a populated table with no backfill.
484
+ enabled Boolean @default(true)
485
+ priority Int @default(0)
486
+ preserveQuery Boolean @default(false)
487
+ // "manual" | "import" | "ai-404-recovery" | "chain-flatten"
488
+ source_kind String @default("manual")
489
+ hits Int @default(0)
490
+ lastHitAt DateTime?
491
+ createdById String?
492
+ createdAt DateTime @default(now())
493
+ updatedAt DateTime @updatedAt
455
494
 
456
495
  @@unique([source])
496
+ @@index([enabled])
497
+ @@index([lastHitAt])
457
498
  @@map("actuate_redirects")
458
499
  }
459
500
 
501
+ // ─── SEO module (0009_seo) ──────────────────────────────────────────
502
+
503
+ enum SeoAuditStatus {
504
+ RUNNING
505
+ COMPLETED
506
+ FAILED
507
+ }
508
+
509
+ enum SeoIssueSeverity {
510
+ critical
511
+ warning
512
+ info
513
+ }
514
+
515
+ enum SeoIssueStatus {
516
+ open
517
+ ignored
518
+ resolved
519
+ recurring
520
+ }
521
+
522
+ enum SeoIssueCategory {
523
+ content
524
+ technical
525
+ redirects
526
+ media
527
+ performance
528
+ schema
529
+ social
530
+ indexing
531
+ }
532
+
533
+ /// One execution of the SEO audit engine. Holds a snapshot of the site
534
+ /// score and issue counts so the Audit tab can show run history without
535
+ /// recomputing. Issues belong to a run via SEOIssue.auditRunId.
536
+ model SeoAuditRun {
537
+ id String @id @default(cuid())
538
+ status SeoAuditStatus @default(RUNNING)
539
+ scope String @default("full")
540
+ startedById String?
541
+ startedAt DateTime @default(now())
542
+ finishedAt DateTime?
543
+ siteScoreBefore Int?
544
+ siteScoreAfter Int?
545
+ pagesScanned Int @default(0)
546
+ issuesFound Int @default(0)
547
+ criticalCount Int @default(0)
548
+ warningCount Int @default(0)
549
+ infoCount Int @default(0)
550
+ errorMessage String?
551
+ metadata Json?
552
+
553
+ issues SeoIssue[]
554
+
555
+ @@index([status])
556
+ @@index([startedAt])
557
+ @@map("actuate_seo_audit_runs")
558
+ }
559
+
560
+ /// A single SEO problem detected by an audit run. `issueKey` is a stable
561
+ /// hash of (type + entityId/url) used to carry status across runs so a
562
+ /// resolved/ignored issue stays resolved/ignored when re-detected.
563
+ model SeoIssue {
564
+ id String @id @default(cuid())
565
+ auditRunId String?
566
+ issueKey String
567
+ entityType String?
568
+ entityId String?
569
+ entityTitle String?
570
+ url String?
571
+ severity SeoIssueSeverity
572
+ category SeoIssueCategory
573
+ type String
574
+ title String
575
+ description String?
576
+ impact String?
577
+ recommendation String?
578
+ status SeoIssueStatus @default(open)
579
+ fixable Boolean @default(false)
580
+ fixActionType String?
581
+ relatedEntityIds Json?
582
+ source String @default("audit")
583
+ firstDetectedAt DateTime @default(now())
584
+ lastDetectedAt DateTime @default(now())
585
+ resolvedAt DateTime?
586
+ resolvedById String?
587
+ metadata Json?
588
+
589
+ auditRun SeoAuditRun? @relation(fields: [auditRunId], references: [id], onDelete: SetNull)
590
+
591
+ @@index([auditRunId])
592
+ @@index([status])
593
+ @@index([severity])
594
+ @@index([category])
595
+ @@index([issueKey])
596
+ @@map("actuate_seo_issues")
597
+ }
598
+
599
+ /// A 404 hit observed by the redirect middleware. Aggregated per path so
600
+ /// the Redirects tab can surface "broken URLs still getting traffic".
601
+ model Redirect404Hit {
602
+ id String @id @default(cuid())
603
+ path String
604
+ hits Int @default(1)
605
+ referrer String?
606
+ resolvedAt DateTime?
607
+ firstSeenAt DateTime @default(now())
608
+ lastSeenAt DateTime @default(now())
609
+
610
+ @@unique([path])
611
+ @@index([lastSeenAt])
612
+ @@map("actuate_redirect_404_hits")
613
+ }
614
+
615
+ /// An AI/heuristic suggestion for a 404 destination. Confidence-scored and
616
+ /// never auto-applied below the configured threshold.
617
+ model RedirectSuggestion {
618
+ id String @id @default(cuid())
619
+ fromPath String
620
+ toPath String
621
+ confidence Int @default(0)
622
+ reason String?
623
+ // "open" | "accepted" | "dismissed"
624
+ status String @default("open")
625
+ source String @default("ai-404-recovery")
626
+ metadata Json?
627
+ createdAt DateTime @default(now())
628
+ updatedAt DateTime @updatedAt
629
+
630
+ @@index([status])
631
+ @@index([fromPath])
632
+ @@map("actuate_redirect_suggestions")
633
+ }
634
+
460
635
  model FormSubmission {
461
636
  id String @id @default(cuid())
462
637
  formId String