@actuate-media/cms-core 0.28.1 → 0.30.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.
- package/dist/__tests__/actions/seo-fields-update.test.d.ts +2 -0
- package/dist/__tests__/actions/seo-fields-update.test.d.ts.map +1 -0
- package/dist/__tests__/actions/seo-fields-update.test.js +93 -0
- package/dist/__tests__/actions/seo-fields-update.test.js.map +1 -0
- package/dist/__tests__/api/llms-txt.test.d.ts +2 -0
- package/dist/__tests__/api/llms-txt.test.d.ts.map +1 -0
- package/dist/__tests__/api/llms-txt.test.js +77 -0
- package/dist/__tests__/api/llms-txt.test.js.map +1 -0
- package/dist/__tests__/api/seo-read-access.test.d.ts +2 -0
- package/dist/__tests__/api/seo-read-access.test.d.ts.map +1 -0
- package/dist/__tests__/api/seo-read-access.test.js +101 -0
- package/dist/__tests__/api/seo-read-access.test.js.map +1 -0
- package/dist/__tests__/api/seo-summary-ssrf.test.d.ts +2 -0
- package/dist/__tests__/api/seo-summary-ssrf.test.d.ts.map +1 -0
- package/dist/__tests__/api/seo-summary-ssrf.test.js +80 -0
- package/dist/__tests__/api/seo-summary-ssrf.test.js.map +1 -0
- package/dist/__tests__/api/sitemap-pagination.test.d.ts +2 -0
- package/dist/__tests__/api/sitemap-pagination.test.d.ts.map +1 -0
- package/dist/__tests__/api/sitemap-pagination.test.js +100 -0
- package/dist/__tests__/api/sitemap-pagination.test.js.map +1 -0
- package/dist/__tests__/media/seo.test.d.ts +2 -0
- package/dist/__tests__/media/seo.test.d.ts.map +1 -0
- package/dist/__tests__/media/seo.test.js +186 -0
- package/dist/__tests__/media/seo.test.js.map +1 -0
- package/dist/__tests__/middleware.test.d.ts +2 -0
- package/dist/__tests__/middleware.test.d.ts.map +1 -0
- package/dist/__tests__/middleware.test.js +50 -0
- package/dist/__tests__/middleware.test.js.map +1 -0
- package/dist/__tests__/redirects/graph.test.d.ts +2 -0
- package/dist/__tests__/redirects/graph.test.d.ts.map +1 -0
- package/dist/__tests__/redirects/graph.test.js +104 -0
- package/dist/__tests__/redirects/graph.test.js.map +1 -0
- package/dist/__tests__/redirects/suggest.test.d.ts +2 -0
- package/dist/__tests__/redirects/suggest.test.d.ts.map +1 -0
- package/dist/__tests__/redirects/suggest.test.js +28 -0
- package/dist/__tests__/redirects/suggest.test.js.map +1 -0
- package/dist/__tests__/seo/analysis-markdown.test.d.ts +2 -0
- package/dist/__tests__/seo/analysis-markdown.test.d.ts.map +1 -0
- package/dist/__tests__/seo/analysis-markdown.test.js +57 -0
- package/dist/__tests__/seo/analysis-markdown.test.js.map +1 -0
- package/dist/__tests__/seo/audit-engine.test.d.ts +2 -0
- package/dist/__tests__/seo/audit-engine.test.d.ts.map +1 -0
- package/dist/__tests__/seo/audit-engine.test.js +152 -0
- package/dist/__tests__/seo/audit-engine.test.js.map +1 -0
- package/dist/__tests__/seo/audit-runner.test.d.ts +2 -0
- package/dist/__tests__/seo/audit-runner.test.d.ts.map +1 -0
- package/dist/__tests__/seo/audit-runner.test.js +82 -0
- package/dist/__tests__/seo/audit-runner.test.js.map +1 -0
- package/dist/__tests__/seo/config-store.test.js +24 -160
- package/dist/__tests__/seo/config-store.test.js.map +1 -1
- package/dist/__tests__/seo/internal-collections.test.d.ts +2 -0
- package/dist/__tests__/seo/internal-collections.test.d.ts.map +1 -0
- package/dist/__tests__/seo/internal-collections.test.js +27 -0
- package/dist/__tests__/seo/internal-collections.test.js.map +1 -0
- package/dist/__tests__/seo/page-meta.test.js +34 -2
- package/dist/__tests__/seo/page-meta.test.js.map +1 -1
- package/dist/__tests__/seo/robots.test.js +23 -2
- package/dist/__tests__/seo/robots.test.js.map +1 -1
- package/dist/__tests__/seo/score.test.d.ts +2 -0
- package/dist/__tests__/seo/score.test.d.ts.map +1 -0
- package/dist/__tests__/seo/score.test.js +152 -0
- package/dist/__tests__/seo/score.test.js.map +1 -0
- package/dist/__tests__/setup/index.test.d.ts +2 -0
- package/dist/__tests__/setup/index.test.d.ts.map +1 -0
- package/dist/__tests__/setup/index.test.js +82 -0
- package/dist/__tests__/setup/index.test.js.map +1 -0
- package/dist/actions.d.ts +24 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +63 -0
- package/dist/actions.js.map +1 -1
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +2339 -274
- package/dist/api/handlers.js.map +1 -1
- package/dist/config/types.d.ts +7 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/media/index.d.ts +2 -0
- package/dist/media/index.d.ts.map +1 -1
- package/dist/media/index.js +1 -0
- package/dist/media/index.js.map +1 -1
- package/dist/media/seo.d.ts +88 -0
- package/dist/media/seo.d.ts.map +1 -0
- package/dist/media/seo.js +215 -0
- package/dist/media/seo.js.map +1 -0
- package/dist/middleware.js +12 -2
- package/dist/middleware.js.map +1 -1
- package/dist/redirects/graph.d.ts +68 -0
- package/dist/redirects/graph.d.ts.map +1 -0
- package/dist/redirects/graph.js +136 -0
- package/dist/redirects/graph.js.map +1 -0
- package/dist/redirects/index.d.ts +5 -0
- package/dist/redirects/index.d.ts.map +1 -0
- package/dist/redirects/index.js +3 -0
- package/dist/redirects/index.js.map +1 -0
- package/dist/redirects/suggest.d.ts +21 -0
- package/dist/redirects/suggest.d.ts.map +1 -0
- package/dist/redirects/suggest.js +55 -0
- package/dist/redirects/suggest.js.map +1 -0
- package/dist/seo/analysis.d.ts +9 -0
- package/dist/seo/analysis.d.ts.map +1 -1
- package/dist/seo/analysis.js +45 -6
- package/dist/seo/analysis.js.map +1 -1
- package/dist/seo/audit-engine.d.ts +119 -0
- package/dist/seo/audit-engine.d.ts.map +1 -0
- package/dist/seo/audit-engine.js +402 -0
- package/dist/seo/audit-engine.js.map +1 -0
- package/dist/seo/audit-runner.d.ts +47 -0
- package/dist/seo/audit-runner.d.ts.map +1 -0
- package/dist/seo/audit-runner.js +252 -0
- package/dist/seo/audit-runner.js.map +1 -0
- package/dist/seo/config-store.d.ts +28 -0
- package/dist/seo/config-store.d.ts.map +1 -1
- package/dist/seo/config-store.js +22 -0
- package/dist/seo/config-store.js.map +1 -1
- package/dist/seo/index.d.ts +8 -3
- package/dist/seo/index.d.ts.map +1 -1
- package/dist/seo/index.js +5 -2
- package/dist/seo/index.js.map +1 -1
- package/dist/seo/page-meta.d.ts +6 -0
- package/dist/seo/page-meta.d.ts.map +1 -1
- package/dist/seo/page-meta.js +22 -5
- package/dist/seo/page-meta.js.map +1 -1
- package/dist/seo/robots.d.ts +7 -0
- package/dist/seo/robots.d.ts.map +1 -1
- package/dist/seo/robots.js +10 -1
- package/dist/seo/robots.js.map +1 -1
- package/dist/seo/score.d.ts +132 -0
- package/dist/seo/score.d.ts.map +1 -0
- package/dist/seo/score.js +216 -0
- package/dist/seo/score.js.map +1 -0
- package/dist/setup/index.d.ts +16 -1
- package/dist/setup/index.d.ts.map +1 -1
- package/dist/setup/index.js +52 -2
- package/dist/setup/index.js.map +1 -1
- package/package.json +1 -1
- package/prisma/migrations/0008_media_center/migration.sql +47 -0
- package/prisma/migrations/0009_seo/migration.sql +143 -0
- package/prisma/schema.prisma +183 -8
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
-- SEO Operations Center
|
|
2
|
+
--
|
|
3
|
+
-- Adds the persistence layer for the SEO module:
|
|
4
|
+
-- * Redirect: runtime + management columns (enabled, priority, preserveQuery,
|
|
5
|
+
-- source_kind, hits, lastHitAt, createdById).
|
|
6
|
+
-- * SeoAuditRun: one row per audit execution (score + issue-count snapshot).
|
|
7
|
+
-- * SeoIssue: detected SEO problems, status carried across runs via issueKey.
|
|
8
|
+
-- * Redirect404Hit: aggregated 404 traffic observed by the middleware.
|
|
9
|
+
-- * RedirectSuggestion: confidence-scored 404 recovery / chain suggestions.
|
|
10
|
+
--
|
|
11
|
+
-- Safe to apply to a populated database:
|
|
12
|
+
-- * Every new Redirect column is nullable or has a default — no backfill.
|
|
13
|
+
-- * New tables/enums are additive; no existing column is dropped or retyped.
|
|
14
|
+
|
|
15
|
+
-- ---------------------------------------------------------------------------
|
|
16
|
+
-- Enums
|
|
17
|
+
-- ---------------------------------------------------------------------------
|
|
18
|
+
CREATE TYPE "SeoAuditStatus" AS ENUM ('RUNNING', 'COMPLETED', 'FAILED');
|
|
19
|
+
CREATE TYPE "SeoIssueSeverity" AS ENUM ('critical', 'warning', 'info');
|
|
20
|
+
CREATE TYPE "SeoIssueStatus" AS ENUM ('open', 'ignored', 'resolved', 'recurring');
|
|
21
|
+
CREATE TYPE "SeoIssueCategory" AS ENUM (
|
|
22
|
+
'content', 'technical', 'redirects', 'media',
|
|
23
|
+
'performance', 'schema', 'social', 'indexing'
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
-- ---------------------------------------------------------------------------
|
|
27
|
+
-- actuate_redirects new columns
|
|
28
|
+
-- ---------------------------------------------------------------------------
|
|
29
|
+
ALTER TABLE "actuate_redirects"
|
|
30
|
+
ADD COLUMN "enabled" BOOLEAN NOT NULL DEFAULT true,
|
|
31
|
+
ADD COLUMN "priority" INTEGER NOT NULL DEFAULT 0,
|
|
32
|
+
ADD COLUMN "preserveQuery" BOOLEAN NOT NULL DEFAULT false,
|
|
33
|
+
ADD COLUMN "source_kind" TEXT NOT NULL DEFAULT 'manual',
|
|
34
|
+
ADD COLUMN "hits" INTEGER NOT NULL DEFAULT 0,
|
|
35
|
+
ADD COLUMN "lastHitAt" TIMESTAMP(3),
|
|
36
|
+
ADD COLUMN "createdById" TEXT;
|
|
37
|
+
|
|
38
|
+
CREATE INDEX "actuate_redirects_enabled_idx" ON "actuate_redirects" ("enabled");
|
|
39
|
+
CREATE INDEX "actuate_redirects_lastHitAt_idx" ON "actuate_redirects" ("lastHitAt");
|
|
40
|
+
|
|
41
|
+
-- ---------------------------------------------------------------------------
|
|
42
|
+
-- actuate_seo_audit_runs
|
|
43
|
+
-- ---------------------------------------------------------------------------
|
|
44
|
+
CREATE TABLE "actuate_seo_audit_runs" (
|
|
45
|
+
"id" TEXT NOT NULL,
|
|
46
|
+
"status" "SeoAuditStatus" NOT NULL DEFAULT 'RUNNING',
|
|
47
|
+
"scope" TEXT NOT NULL DEFAULT 'full',
|
|
48
|
+
"startedById" TEXT,
|
|
49
|
+
"startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
50
|
+
"finishedAt" TIMESTAMP(3),
|
|
51
|
+
"siteScoreBefore" INTEGER,
|
|
52
|
+
"siteScoreAfter" INTEGER,
|
|
53
|
+
"pagesScanned" INTEGER NOT NULL DEFAULT 0,
|
|
54
|
+
"issuesFound" INTEGER NOT NULL DEFAULT 0,
|
|
55
|
+
"criticalCount" INTEGER NOT NULL DEFAULT 0,
|
|
56
|
+
"warningCount" INTEGER NOT NULL DEFAULT 0,
|
|
57
|
+
"infoCount" INTEGER NOT NULL DEFAULT 0,
|
|
58
|
+
"errorMessage" TEXT,
|
|
59
|
+
"metadata" JSONB,
|
|
60
|
+
CONSTRAINT "actuate_seo_audit_runs_pkey" PRIMARY KEY ("id")
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
CREATE INDEX "actuate_seo_audit_runs_status_idx" ON "actuate_seo_audit_runs" ("status");
|
|
64
|
+
CREATE INDEX "actuate_seo_audit_runs_startedAt_idx" ON "actuate_seo_audit_runs" ("startedAt");
|
|
65
|
+
|
|
66
|
+
-- ---------------------------------------------------------------------------
|
|
67
|
+
-- actuate_seo_issues
|
|
68
|
+
-- ---------------------------------------------------------------------------
|
|
69
|
+
CREATE TABLE "actuate_seo_issues" (
|
|
70
|
+
"id" TEXT NOT NULL,
|
|
71
|
+
"auditRunId" TEXT,
|
|
72
|
+
"issueKey" TEXT NOT NULL,
|
|
73
|
+
"entityType" TEXT,
|
|
74
|
+
"entityId" TEXT,
|
|
75
|
+
"entityTitle" TEXT,
|
|
76
|
+
"url" TEXT,
|
|
77
|
+
"severity" "SeoIssueSeverity" NOT NULL,
|
|
78
|
+
"category" "SeoIssueCategory" NOT NULL,
|
|
79
|
+
"type" TEXT NOT NULL,
|
|
80
|
+
"title" TEXT NOT NULL,
|
|
81
|
+
"description" TEXT,
|
|
82
|
+
"impact" TEXT,
|
|
83
|
+
"recommendation" TEXT,
|
|
84
|
+
"status" "SeoIssueStatus" NOT NULL DEFAULT 'open',
|
|
85
|
+
"fixable" BOOLEAN NOT NULL DEFAULT false,
|
|
86
|
+
"fixActionType" TEXT,
|
|
87
|
+
"relatedEntityIds" JSONB,
|
|
88
|
+
"source" TEXT NOT NULL DEFAULT 'audit',
|
|
89
|
+
"firstDetectedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
90
|
+
"lastDetectedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
91
|
+
"resolvedAt" TIMESTAMP(3),
|
|
92
|
+
"resolvedById" TEXT,
|
|
93
|
+
"metadata" JSONB,
|
|
94
|
+
CONSTRAINT "actuate_seo_issues_pkey" PRIMARY KEY ("id")
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
CREATE INDEX "actuate_seo_issues_auditRunId_idx" ON "actuate_seo_issues" ("auditRunId");
|
|
98
|
+
CREATE INDEX "actuate_seo_issues_status_idx" ON "actuate_seo_issues" ("status");
|
|
99
|
+
CREATE INDEX "actuate_seo_issues_severity_idx" ON "actuate_seo_issues" ("severity");
|
|
100
|
+
CREATE INDEX "actuate_seo_issues_category_idx" ON "actuate_seo_issues" ("category");
|
|
101
|
+
CREATE INDEX "actuate_seo_issues_issueKey_idx" ON "actuate_seo_issues" ("issueKey");
|
|
102
|
+
|
|
103
|
+
ALTER TABLE "actuate_seo_issues"
|
|
104
|
+
ADD CONSTRAINT "actuate_seo_issues_auditRunId_fkey"
|
|
105
|
+
FOREIGN KEY ("auditRunId") REFERENCES "actuate_seo_audit_runs"("id")
|
|
106
|
+
ON DELETE SET NULL ON UPDATE CASCADE;
|
|
107
|
+
|
|
108
|
+
-- ---------------------------------------------------------------------------
|
|
109
|
+
-- actuate_redirect_404_hits
|
|
110
|
+
-- ---------------------------------------------------------------------------
|
|
111
|
+
CREATE TABLE "actuate_redirect_404_hits" (
|
|
112
|
+
"id" TEXT NOT NULL,
|
|
113
|
+
"path" TEXT NOT NULL,
|
|
114
|
+
"hits" INTEGER NOT NULL DEFAULT 1,
|
|
115
|
+
"referrer" TEXT,
|
|
116
|
+
"resolvedAt" TIMESTAMP(3),
|
|
117
|
+
"firstSeenAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
118
|
+
"lastSeenAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
119
|
+
CONSTRAINT "actuate_redirect_404_hits_pkey" PRIMARY KEY ("id")
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
CREATE UNIQUE INDEX "actuate_redirect_404_hits_path_key" ON "actuate_redirect_404_hits" ("path");
|
|
123
|
+
CREATE INDEX "actuate_redirect_404_hits_lastSeenAt_idx" ON "actuate_redirect_404_hits" ("lastSeenAt");
|
|
124
|
+
|
|
125
|
+
-- ---------------------------------------------------------------------------
|
|
126
|
+
-- actuate_redirect_suggestions
|
|
127
|
+
-- ---------------------------------------------------------------------------
|
|
128
|
+
CREATE TABLE "actuate_redirect_suggestions" (
|
|
129
|
+
"id" TEXT NOT NULL,
|
|
130
|
+
"fromPath" TEXT NOT NULL,
|
|
131
|
+
"toPath" TEXT NOT NULL,
|
|
132
|
+
"confidence" INTEGER NOT NULL DEFAULT 0,
|
|
133
|
+
"reason" TEXT,
|
|
134
|
+
"status" TEXT NOT NULL DEFAULT 'open',
|
|
135
|
+
"source" TEXT NOT NULL DEFAULT 'ai-404-recovery',
|
|
136
|
+
"metadata" JSONB,
|
|
137
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
138
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
139
|
+
CONSTRAINT "actuate_redirect_suggestions_pkey" PRIMARY KEY ("id")
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
CREATE INDEX "actuate_redirect_suggestions_status_idx" ON "actuate_redirect_suggestions" ("status");
|
|
143
|
+
CREATE INDEX "actuate_redirect_suggestions_fromPath_idx" ON "actuate_redirect_suggestions" ("fromPath");
|
package/prisma/schema.prisma
CHANGED
|
@@ -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
|
|
448
|
-
source
|
|
449
|
-
destination
|
|
450
|
-
statusCode
|
|
451
|
-
isRegex
|
|
452
|
-
notes
|
|
453
|
-
|
|
454
|
-
|
|
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
|