@prenta/core 7.7.0 → 7.7.2
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/CHANGELOG.md +12 -0
- package/dist/api/route-helpers/auth.d.ts +131 -0
- package/dist/api/route-helpers/auth.d.ts.map +1 -0
- package/dist/api/route-helpers/auth.js +505 -0
- package/dist/api/route-helpers/auth.js.map +1 -0
- package/dist/api/route-helpers/authoring.d.ts +32 -0
- package/dist/api/route-helpers/authoring.d.ts.map +1 -0
- package/dist/api/route-helpers/authoring.js +99 -0
- package/dist/api/route-helpers/authoring.js.map +1 -0
- package/dist/api/route-helpers/forms.d.ts +46 -0
- package/dist/api/route-helpers/forms.d.ts.map +1 -0
- package/dist/api/route-helpers/forms.js +435 -0
- package/dist/api/route-helpers/forms.js.map +1 -0
- package/dist/api/route-helpers/http.d.ts +87 -0
- package/dist/api/route-helpers/http.d.ts.map +1 -0
- package/dist/api/route-helpers/http.js +286 -0
- package/dist/api/route-helpers/http.js.map +1 -0
- package/dist/api/route-helpers/media.d.ts +37 -0
- package/dist/api/route-helpers/media.d.ts.map +1 -0
- package/dist/api/route-helpers/media.js +189 -0
- package/dist/api/route-helpers/media.js.map +1 -0
- package/dist/api/route-helpers/page-shape.d.ts +31 -0
- package/dist/api/route-helpers/page-shape.d.ts.map +1 -0
- package/dist/api/route-helpers/page-shape.js +98 -0
- package/dist/api/route-helpers/page-shape.js.map +1 -0
- package/dist/api/route-helpers.d.ts +8 -358
- package/dist/api/route-helpers.d.ts.map +1 -1
- package/dist/api/route-helpers.js +7 -1592
- package/dist/api/route-helpers.js.map +1 -1
- package/dist/api/routes/seo/handlers/analysis.d.ts +4 -0
- package/dist/api/routes/seo/handlers/analysis.d.ts.map +1 -0
- package/dist/api/routes/seo/handlers/analysis.js +131 -0
- package/dist/api/routes/seo/handlers/analysis.js.map +1 -0
- package/dist/api/routes/seo/handlers/audits.d.ts +5 -0
- package/dist/api/routes/seo/handlers/audits.d.ts.map +1 -0
- package/dist/api/routes/seo/handlers/audits.js +121 -0
- package/dist/api/routes/seo/handlers/audits.js.map +1 -0
- package/dist/api/routes/seo/handlers/content.d.ts +4 -0
- package/dist/api/routes/seo/handlers/content.d.ts.map +1 -0
- package/dist/api/routes/seo/handlers/content.js +330 -0
- package/dist/api/routes/seo/handlers/content.js.map +1 -0
- package/dist/api/routes/seo/handlers/issue-helpers.d.ts +37 -0
- package/dist/api/routes/seo/handlers/issue-helpers.d.ts.map +1 -0
- package/dist/api/routes/seo/handlers/issue-helpers.js +72 -0
- package/dist/api/routes/seo/handlers/issue-helpers.js.map +1 -0
- package/dist/api/routes/seo/handlers/issues-briefs.d.ts +7 -0
- package/dist/api/routes/seo/handlers/issues-briefs.d.ts.map +1 -0
- package/dist/api/routes/seo/handlers/issues-briefs.js +341 -0
- package/dist/api/routes/seo/handlers/issues-briefs.js.map +1 -0
- package/dist/api/routes/seo/handlers/issues-fixes.d.ts +4 -0
- package/dist/api/routes/seo/handlers/issues-fixes.d.ts.map +1 -0
- package/dist/api/routes/seo/handlers/issues-fixes.js +233 -0
- package/dist/api/routes/seo/handlers/issues-fixes.js.map +1 -0
- package/dist/api/routes/seo/handlers/issues-list.d.ts +7 -0
- package/dist/api/routes/seo/handlers/issues-list.d.ts.map +1 -0
- package/dist/api/routes/seo/handlers/issues-list.js +191 -0
- package/dist/api/routes/seo/handlers/issues-list.js.map +1 -0
- package/dist/api/routes/seo/handlers/links.d.ts +6 -0
- package/dist/api/routes/seo/handlers/links.d.ts.map +1 -0
- package/dist/api/routes/seo/handlers/links.js +383 -0
- package/dist/api/routes/seo/handlers/links.js.map +1 -0
- package/dist/api/routes/seo/handlers/overview.d.ts +4 -0
- package/dist/api/routes/seo/handlers/overview.d.ts.map +1 -0
- package/dist/api/routes/seo/handlers/overview.js +348 -0
- package/dist/api/routes/seo/handlers/overview.js.map +1 -0
- package/dist/api/routes/seo/handlers/pages.d.ts +2 -0
- package/dist/api/routes/seo/handlers/pages.d.ts.map +1 -0
- package/dist/api/routes/seo/handlers/pages.js +38 -0
- package/dist/api/routes/seo/handlers/pages.js.map +1 -0
- package/dist/api/routes/seo/handlers/proposals.d.ts +4 -0
- package/dist/api/routes/seo/handlers/proposals.d.ts.map +1 -0
- package/dist/api/routes/seo/handlers/proposals.js +93 -0
- package/dist/api/routes/seo/handlers/proposals.js.map +1 -0
- package/dist/api/routes/seo/handlers/public-files.d.ts +6 -0
- package/dist/api/routes/seo/handlers/public-files.d.ts.map +1 -0
- package/dist/api/routes/seo/handlers/public-files.js +328 -0
- package/dist/api/routes/seo/handlers/public-files.js.map +1 -0
- package/dist/api/routes/seo/handlers/shared.d.ts +24 -0
- package/dist/api/routes/seo/handlers/shared.d.ts.map +1 -0
- package/dist/api/routes/seo/handlers/shared.js +67 -0
- package/dist/api/routes/seo/handlers/shared.js.map +1 -0
- package/dist/api/routes/seo.d.ts +5 -5
- package/dist/api/routes/seo.d.ts.map +1 -1
- package/dist/api/routes/seo.js +56 -2610
- package/dist/api/routes/seo.js.map +1 -1
- package/dist/api/routes/users/handlers/api-keys.d.ts +4 -0
- package/dist/api/routes/users/handlers/api-keys.d.ts.map +1 -0
- package/dist/api/routes/users/handlers/api-keys.js +164 -0
- package/dist/api/routes/users/handlers/api-keys.js.map +1 -0
- package/dist/api/routes/users/handlers/invites.d.ts +7 -0
- package/dist/api/routes/users/handlers/invites.d.ts.map +1 -0
- package/dist/api/routes/users/handlers/invites.js +201 -0
- package/dist/api/routes/users/handlers/invites.js.map +1 -0
- package/dist/api/routes/users/handlers/users-admin.d.ts +7 -0
- package/dist/api/routes/users/handlers/users-admin.d.ts.map +1 -0
- package/dist/api/routes/users/handlers/users-admin.js +310 -0
- package/dist/api/routes/users/handlers/users-admin.js.map +1 -0
- package/dist/api/routes/users/handlers/users-detail.d.ts +16 -0
- package/dist/api/routes/users/handlers/users-detail.d.ts.map +1 -0
- package/dist/api/routes/users/handlers/users-detail.js +525 -0
- package/dist/api/routes/users/handlers/users-detail.js.map +1 -0
- package/dist/api/routes/users.d.ts +4 -0
- package/dist/api/routes/users.d.ts.map +1 -1
- package/dist/api/routes/users.js +38 -1177
- package/dist/api/routes/users.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { updateDocumentSeoFields } from '../../../../actions.js';
|
|
2
|
+
import { logEvent } from '../../../../security/audit.js';
|
|
3
|
+
import { DEFAULT_DIRECTIVES, coerceMaxImagePreview } from '../../../../seo/robots-directives.js';
|
|
4
|
+
import { getPrentaConfig } from '../../../../config/runtime.js';
|
|
5
|
+
import { syncSchemaTypeFields } from '../../../../seo/schema-sync.js';
|
|
6
|
+
import { json, errorResponse, internalError, asRecord, requireAuth, buildActionContext, WRITE_ROLES, requireRole, enforceDocumentReadAccess, clampPage, clampPageSize, } from '../../../route-helpers.js';
|
|
7
|
+
import { db } from './shared.js';
|
|
8
|
+
/**
|
|
9
|
+
* Compute the set of front-end content collections the caller may read.
|
|
10
|
+
* Mirrors the per-document `enforceDocumentReadAccess` check the sibling
|
|
11
|
+
* `/seo/analysis|readability|…` endpoints run, but resolved once for the
|
|
12
|
+
* collection list so the aggregate `/seo/content` view can never surface SEO
|
|
13
|
+
* fields for a collection the caller's scope/role forbids.
|
|
14
|
+
*/
|
|
15
|
+
async function readableSeoCollections(session) {
|
|
16
|
+
const { frontEndContentCollectionTypes } = await import('../../../../seo/audit-runner.js');
|
|
17
|
+
const collectionTypes = frontEndContentCollectionTypes(getPrentaConfig());
|
|
18
|
+
const allowed = new Set();
|
|
19
|
+
for (const slug of Object.keys(collectionTypes)) {
|
|
20
|
+
const err = await enforceDocumentReadAccess(session, slug);
|
|
21
|
+
if (!err)
|
|
22
|
+
allowed.add(slug);
|
|
23
|
+
}
|
|
24
|
+
return allowed;
|
|
25
|
+
}
|
|
26
|
+
async function buildSeoContentRecords(allowedCollections,
|
|
27
|
+
/**
|
|
28
|
+
* Restrict to specific documents, regardless of publication status. Used by
|
|
29
|
+
* the per-document GET, which must answer for a saved draft — the list
|
|
30
|
+
* endpoints deliberately stay published-only.
|
|
31
|
+
*/
|
|
32
|
+
documentIds, page) {
|
|
33
|
+
const { gatherAuditEntities, countAuditEntities, frontEndContentCollectionTypes } = await import('../../../../seo/audit-runner.js');
|
|
34
|
+
const { applySeoOverrides, getSeoOverrides } = await import('../../../../seo/config-store.js');
|
|
35
|
+
const { calculatePageSeoScore } = await import('../../../../seo/score.js');
|
|
36
|
+
const allTypes = frontEndContentCollectionTypes(getPrentaConfig());
|
|
37
|
+
// When a readable-collection allow-list is supplied, scope the gather to it
|
|
38
|
+
// so records for forbidden collections are never even built.
|
|
39
|
+
const collectionTypes = allowedCollections
|
|
40
|
+
? Object.fromEntries(Object.entries(allTypes).filter(([slug]) => allowedCollections.has(slug)))
|
|
41
|
+
: allTypes;
|
|
42
|
+
const overrides = await getSeoOverrides(db()).catch(() => null);
|
|
43
|
+
const siteSeo = applySeoOverrides(getPrentaConfig(), overrides)?.seo;
|
|
44
|
+
const [entities, total] = await Promise.all([
|
|
45
|
+
gatherAuditEntities(db(), {
|
|
46
|
+
collectionTypes,
|
|
47
|
+
documentIds,
|
|
48
|
+
skip: page?.skip,
|
|
49
|
+
take: page?.take,
|
|
50
|
+
}),
|
|
51
|
+
documentIds
|
|
52
|
+
? Promise.resolve(documentIds.length)
|
|
53
|
+
: countAuditEntities(db(), { collectionTypes }),
|
|
54
|
+
]);
|
|
55
|
+
const records = entities.map((e) => {
|
|
56
|
+
const score = calculatePageSeoScore(e);
|
|
57
|
+
return {
|
|
58
|
+
id: `${e.entityType}:${e.entityId}`,
|
|
59
|
+
entityType: e.entityType,
|
|
60
|
+
entityId: e.entityId,
|
|
61
|
+
title: e.title,
|
|
62
|
+
url: e.url,
|
|
63
|
+
canonicalUrl: e.canonicalUrl ?? null,
|
|
64
|
+
/** Rendered public title (after site title template). */
|
|
65
|
+
metaTitle: e.metaTitle ?? null,
|
|
66
|
+
/** Stored page-specific portion (`%page%` token). */
|
|
67
|
+
metaTitlePage: e.metaTitlePage ?? null,
|
|
68
|
+
siteName: siteSeo?.siteName ?? null,
|
|
69
|
+
titleTemplate: siteSeo?.titleTemplate ?? null,
|
|
70
|
+
metaDescription: e.metaDescription ?? null,
|
|
71
|
+
focusKeyword: e.focusKeyword ?? null,
|
|
72
|
+
keyTakeaway: e.keyTakeaway ?? null,
|
|
73
|
+
ogTitle: e.ogTitle ?? null,
|
|
74
|
+
ogDescription: e.ogDescription ?? null,
|
|
75
|
+
ogImageId: null,
|
|
76
|
+
ogImage: e.ogImage ?? null,
|
|
77
|
+
twitterTitle: null,
|
|
78
|
+
twitterDescription: null,
|
|
79
|
+
twitterImageId: null,
|
|
80
|
+
noindex: e.noindex ?? false,
|
|
81
|
+
nofollow: false,
|
|
82
|
+
// Permissive defaults; the single-record GET overlays what is stored.
|
|
83
|
+
nosnippet: false,
|
|
84
|
+
noarchive: false,
|
|
85
|
+
maxImagePreview: DEFAULT_DIRECTIVES.maxImagePreview,
|
|
86
|
+
structuredDataType: e.structuredDataType ?? null,
|
|
87
|
+
structuredData: null,
|
|
88
|
+
// llms.txt curation defaults; the single-record GET overlays the real
|
|
89
|
+
// stored values (the list endpoint doesn't surface these).
|
|
90
|
+
llmsInclude: 'AUTO',
|
|
91
|
+
llmsDescription: null,
|
|
92
|
+
llmsSection: null,
|
|
93
|
+
llmsOptional: false,
|
|
94
|
+
seoScore: score.score,
|
|
95
|
+
seoGrade: score.grade,
|
|
96
|
+
updatedAt: e.updatedAt instanceof Date
|
|
97
|
+
? e.updatedAt.toISOString()
|
|
98
|
+
: typeof e.updatedAt === 'string'
|
|
99
|
+
? e.updatedAt
|
|
100
|
+
: null,
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
return {
|
|
104
|
+
records,
|
|
105
|
+
total,
|
|
106
|
+
truncated: !documentIds && total > records.length,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
export async function getSeoContent(request) {
|
|
110
|
+
try {
|
|
111
|
+
const auth = await requireAuth(request);
|
|
112
|
+
if (auth.error)
|
|
113
|
+
return auth.error;
|
|
114
|
+
// Scope the list to the collections this caller may read — otherwise a
|
|
115
|
+
// collection-scoped API key (or a collection-restricted role) could pull
|
|
116
|
+
// metaTitle/description/canonical/OG for every collection, the exact
|
|
117
|
+
// bypass the sibling document-level SEO endpoints already prevent.
|
|
118
|
+
const allowed = await readableSeoCollections(auth.session);
|
|
119
|
+
const url = new URL(request.url);
|
|
120
|
+
const page = clampPage(url.searchParams.get('page'));
|
|
121
|
+
const pageSize = clampPageSize(url.searchParams.get('pageSize'), 100, 50);
|
|
122
|
+
const result = await buildSeoContentRecords(allowed, undefined, {
|
|
123
|
+
skip: (page - 1) * pageSize,
|
|
124
|
+
take: pageSize,
|
|
125
|
+
});
|
|
126
|
+
return json({
|
|
127
|
+
data: {
|
|
128
|
+
records: result.records,
|
|
129
|
+
total: result.total,
|
|
130
|
+
page,
|
|
131
|
+
pageSize,
|
|
132
|
+
truncated: result.truncated,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
return internalError(err, 'seo/content');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export async function getSeoContentRecord(request, params) {
|
|
141
|
+
try {
|
|
142
|
+
const auth = await requireAuth(request);
|
|
143
|
+
if (auth.error)
|
|
144
|
+
return auth.error;
|
|
145
|
+
// Load the target first so we can enforce the collection's read-access
|
|
146
|
+
// rule (requireCollectionScope + assertCollectionAccess) before returning
|
|
147
|
+
// any SEO fields — parity with /seo/analysis and friends.
|
|
148
|
+
const doc = await db().document.findFirst({
|
|
149
|
+
where: { id: params.entityId, deletedAt: null },
|
|
150
|
+
select: { data: true, collection: true },
|
|
151
|
+
});
|
|
152
|
+
if (!doc)
|
|
153
|
+
return errorResponse('Content record not found', 404);
|
|
154
|
+
const accessErr = await enforceDocumentReadAccess(auth.session, doc.collection);
|
|
155
|
+
if (accessErr)
|
|
156
|
+
return accessErr;
|
|
157
|
+
// Scoped to THIS document and not filtered by publication status: a saved
|
|
158
|
+
// draft has SEO worth editing, and the matching PUT accepts one. Resolving
|
|
159
|
+
// through the published-only gather made drafts 404 here while remaining
|
|
160
|
+
// writable — so an editor could save SEO and never read it back.
|
|
161
|
+
const { records } = await buildSeoContentRecords(null, [params.entityId]);
|
|
162
|
+
const record = records.find((r) => r.entityType === params.entityType && r.entityId === params.entityId);
|
|
163
|
+
if (!record)
|
|
164
|
+
return errorResponse('Content record not found', 404);
|
|
165
|
+
// Overlay the real llms.txt curation values from the stored document
|
|
166
|
+
// (the audit-entity pipeline that builds the base record doesn't carry
|
|
167
|
+
// these convention keys).
|
|
168
|
+
const data = asRecord(doc.data);
|
|
169
|
+
const includeRaw = String(data.llmsInclude ?? '').toUpperCase();
|
|
170
|
+
const merged = {
|
|
171
|
+
...record,
|
|
172
|
+
llmsInclude: includeRaw === 'ALWAYS' || includeRaw === 'NEVER'
|
|
173
|
+
? includeRaw
|
|
174
|
+
: 'AUTO',
|
|
175
|
+
llmsDescription: typeof data.llmsDescription === 'string' ? data.llmsDescription : null,
|
|
176
|
+
llmsSection: typeof data.llmsSection === 'string' ? data.llmsSection : null,
|
|
177
|
+
llmsOptional: data.llmsOptional === true,
|
|
178
|
+
// Robots directives live in the same content JSON. Overlaid here for
|
|
179
|
+
// the same reason as the llms.txt keys: the audit-entity pipeline that
|
|
180
|
+
// builds the base record does not carry them, so without this the
|
|
181
|
+
// editor would read every page back as fully permissive and silently
|
|
182
|
+
// overwrite a real restriction on the next save.
|
|
183
|
+
nosnippet: data.nosnippet === true,
|
|
184
|
+
noarchive: data.noarchive === true,
|
|
185
|
+
maxImagePreview: coerceMaxImagePreview(data.maxImagePreview),
|
|
186
|
+
};
|
|
187
|
+
return json({ data: merged });
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
return internalError(err, 'seo/content/:id');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
export async function putSeoContentRecord(request, params) {
|
|
194
|
+
try {
|
|
195
|
+
const auth = await requireAuth(request);
|
|
196
|
+
if (auth.error)
|
|
197
|
+
return auth.error;
|
|
198
|
+
const roleErr = requireRole(auth.session.role, WRITE_ROLES);
|
|
199
|
+
if (roleErr)
|
|
200
|
+
return roleErr;
|
|
201
|
+
const body = (await request.json().catch(() => null));
|
|
202
|
+
if (!body || typeof body !== 'object') {
|
|
203
|
+
return errorResponse('Request body must be an object', 400);
|
|
204
|
+
}
|
|
205
|
+
const doc = await db().document.findFirst({
|
|
206
|
+
where: { id: params.entityId, deletedAt: null },
|
|
207
|
+
select: { id: true, collection: true },
|
|
208
|
+
});
|
|
209
|
+
if (!doc)
|
|
210
|
+
return errorResponse('Document not found', 404);
|
|
211
|
+
// String SEO fields: payload key -> stored data key, each with a max
|
|
212
|
+
// length so we never persist unbounded/megabyte values into the content
|
|
213
|
+
// JSON. Canonical is stored as `canonical` to match normalizeSeoPage.
|
|
214
|
+
const TEXT_FIELDS = {
|
|
215
|
+
metaTitle: { target: 'metaTitle', max: 300 },
|
|
216
|
+
metaDescription: { target: 'metaDescription', max: 1000 },
|
|
217
|
+
canonicalUrl: { target: 'canonical', max: 2000 },
|
|
218
|
+
focusKeyword: { target: 'focusKeyword', max: 200 },
|
|
219
|
+
keyTakeaway: { target: 'keyTakeaway', max: 500 },
|
|
220
|
+
ogTitle: { target: 'ogTitle', max: 300 },
|
|
221
|
+
ogDescription: { target: 'ogDescription', max: 1000 },
|
|
222
|
+
ogImage: { target: 'ogImage', max: 2000 },
|
|
223
|
+
twitterTitle: { target: 'twitterTitle', max: 300 },
|
|
224
|
+
twitterDescription: { target: 'twitterDescription', max: 1000 },
|
|
225
|
+
twitterImage: { target: 'twitterImage', max: 2000 },
|
|
226
|
+
structuredDataType: { target: 'structuredDataType', max: 100 },
|
|
227
|
+
schemaType: { target: 'schemaType', max: 100 },
|
|
228
|
+
llmsDescription: { target: 'llmsDescription', max: 500 },
|
|
229
|
+
llmsSection: { target: 'llmsSection', max: 100 },
|
|
230
|
+
};
|
|
231
|
+
const patch = {};
|
|
232
|
+
for (const [key, { target, max }] of Object.entries(TEXT_FIELDS)) {
|
|
233
|
+
if (!(key in body))
|
|
234
|
+
continue;
|
|
235
|
+
const value = body[key];
|
|
236
|
+
if (value === null || value === '') {
|
|
237
|
+
patch[target] = '';
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (typeof value !== 'string') {
|
|
241
|
+
return errorResponse(`Field "${key}" must be a string`, 400);
|
|
242
|
+
}
|
|
243
|
+
if (value.length > max) {
|
|
244
|
+
return errorResponse(`Field "${key}" exceeds the ${max}-character limit`, 400);
|
|
245
|
+
}
|
|
246
|
+
patch[target] = value;
|
|
247
|
+
}
|
|
248
|
+
if ('noindex' in body)
|
|
249
|
+
patch.noindex = body.noindex === true;
|
|
250
|
+
if ('nofollow' in body)
|
|
251
|
+
patch.nofollow = body.nofollow === true;
|
|
252
|
+
if ('nosnippet' in body)
|
|
253
|
+
patch.nosnippet = body.nosnippet === true;
|
|
254
|
+
if ('noarchive' in body)
|
|
255
|
+
patch.noarchive = body.noarchive === true;
|
|
256
|
+
if ('maxImagePreview' in body) {
|
|
257
|
+
// Coerced rather than rejected: the field is an enum with a permissive
|
|
258
|
+
// default, and refusing the write would block an otherwise valid patch
|
|
259
|
+
// over a value the composer already knows how to fall back from.
|
|
260
|
+
patch.maxImagePreview = coerceMaxImagePreview(body.maxImagePreview);
|
|
261
|
+
}
|
|
262
|
+
if ('llmsOptional' in body)
|
|
263
|
+
patch.llmsOptional = body.llmsOptional === true;
|
|
264
|
+
if ('llmsInclude' in body) {
|
|
265
|
+
const v = String(body.llmsInclude ?? '').toUpperCase();
|
|
266
|
+
if (v !== 'AUTO' && v !== 'ALWAYS' && v !== 'NEVER') {
|
|
267
|
+
return errorResponse('llmsInclude must be one of AUTO, ALWAYS, NEVER', 400);
|
|
268
|
+
}
|
|
269
|
+
// AUTO is the default — store empty so the field falls back cleanly.
|
|
270
|
+
patch.llmsInclude = v === 'AUTO' ? '' : v;
|
|
271
|
+
}
|
|
272
|
+
if ('llmsPriority' in body) {
|
|
273
|
+
const n = body.llmsPriority;
|
|
274
|
+
if (n !== null && typeof n !== 'number') {
|
|
275
|
+
return errorResponse('llmsPriority must be a number', 400);
|
|
276
|
+
}
|
|
277
|
+
patch.llmsPriority = n ?? 0;
|
|
278
|
+
}
|
|
279
|
+
if ('structuredData' in body) {
|
|
280
|
+
const sd = body.structuredData;
|
|
281
|
+
if (sd !== null && (typeof sd !== 'object' || Array.isArray(sd))) {
|
|
282
|
+
return errorResponse('structuredData must be a JSON object', 400);
|
|
283
|
+
}
|
|
284
|
+
if (sd && JSON.stringify(sd).length > 50_000) {
|
|
285
|
+
return errorResponse('structuredData payload is too large', 400);
|
|
286
|
+
}
|
|
287
|
+
patch.structuredData = sd;
|
|
288
|
+
}
|
|
289
|
+
if (Object.keys(patch).length === 0) {
|
|
290
|
+
return errorResponse('No recognized SEO fields in request body', 400);
|
|
291
|
+
}
|
|
292
|
+
if ('structuredDataType' in patch || 'schemaType' in patch) {
|
|
293
|
+
Object.assign(patch, syncSchemaTypeFields(patch));
|
|
294
|
+
}
|
|
295
|
+
// Route through the data layer so the edit gets per-collection access
|
|
296
|
+
// control, a Version snapshot, search re-indexing and the afterUpdate
|
|
297
|
+
// hook — never write document.data directly. (updateDocumentSeoFields
|
|
298
|
+
// preserves the undeclared SEO convention keys that the generic
|
|
299
|
+
// updateDocument field-access layer would strip.)
|
|
300
|
+
const ctx = buildActionContext(auth.session, db());
|
|
301
|
+
try {
|
|
302
|
+
await updateDocumentSeoFields(doc.collection, doc.id, patch, ctx);
|
|
303
|
+
}
|
|
304
|
+
catch (err) {
|
|
305
|
+
const msg = err instanceof Error ? err.message : '';
|
|
306
|
+
if (msg.includes('Access denied'))
|
|
307
|
+
return errorResponse('Insufficient permissions', 403);
|
|
308
|
+
if (msg.includes('not found'))
|
|
309
|
+
return errorResponse('Document not found', 404);
|
|
310
|
+
throw err;
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
await logEvent({
|
|
314
|
+
event: 'document_updated',
|
|
315
|
+
userId: auth.session.userId,
|
|
316
|
+
details: { action: 'seo_fields_update', documentId: doc.id },
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
/* noop */
|
|
321
|
+
}
|
|
322
|
+
const { records } = await buildSeoContentRecords(null, [doc.id]);
|
|
323
|
+
const record = records.find((r) => r.entityId === doc.id);
|
|
324
|
+
return json({ data: record ?? { ok: true } });
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
return internalError(err, 'seo/content PUT');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
//# sourceMappingURL=content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.js","sourceRoot":"","sources":["../../../../../src/api/routes/seo/handlers/content.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export declare function seoIssueToApi(i: any): {
|
|
2
|
+
id: any;
|
|
3
|
+
auditRunId: any;
|
|
4
|
+
entityType: any;
|
|
5
|
+
entityId: any;
|
|
6
|
+
entityTitle: any;
|
|
7
|
+
url: any;
|
|
8
|
+
severity: any;
|
|
9
|
+
category: any;
|
|
10
|
+
type: any;
|
|
11
|
+
title: any;
|
|
12
|
+
description: any;
|
|
13
|
+
impact: any;
|
|
14
|
+
recommendation: any;
|
|
15
|
+
status: any;
|
|
16
|
+
fixable: any;
|
|
17
|
+
fixActionType: any;
|
|
18
|
+
relatedEntityIds: any;
|
|
19
|
+
source: any;
|
|
20
|
+
firstDetectedAt: any;
|
|
21
|
+
lastDetectedAt: any;
|
|
22
|
+
resolvedAt: any;
|
|
23
|
+
};
|
|
24
|
+
export declare function setIssueStatus(request: Request, issueId: string, status: 'resolved' | 'ignored' | 'open'): Promise<Response>;
|
|
25
|
+
/** Load the audit entity + collection for an issue's linked document. */
|
|
26
|
+
export declare function loadIssueFixContext(issue: {
|
|
27
|
+
entityType?: string | null;
|
|
28
|
+
entityId?: string | null;
|
|
29
|
+
}): Promise<{
|
|
30
|
+
error: Response;
|
|
31
|
+
} | {
|
|
32
|
+
collection: string;
|
|
33
|
+
entity: import('../../../../seo/audit-engine.js').AuditEntityInput;
|
|
34
|
+
updatedAt: Date;
|
|
35
|
+
siteSeo: import('../../../../config/types.js').SiteSEOConfig | undefined;
|
|
36
|
+
}>;
|
|
37
|
+
//# sourceMappingURL=issue-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue-helpers.d.ts","sourceRoot":"","sources":["../../../../../src/api/routes/seo/handlers/issue-helpers.ts"],"names":[],"mappings":"AAeA,wBAAgB,aAAa,CAAC,CAAC,EAAE,GAAG;;;;;;;;;;;;;;;;;;;;;;EAwBnC;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,MAAM,GACtC,OAAO,CAAC,QAAQ,CAAC,CA0BnB;AAED,yEAAyE;AACzE,wBAAsB,mBAAmB,CAAC,KAAK,EAAE;IAC/C,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB,GAAG,OAAO,CACP;IAAE,KAAK,EAAE,QAAQ,CAAA;CAAE,GACnB;IACE,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,OAAO,iCAAiC,EAAE,gBAAgB,CAAA;IAClE,SAAS,EAAE,IAAI,CAAA;IACf,OAAO,EAAE,OAAO,6BAA6B,EAAE,aAAa,GAAG,SAAS,CAAA;CACzE,CACJ,CAKA"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { logEvent } from '../../../../security/audit.js';
|
|
2
|
+
import { json, errorResponse, hasModel, modelNotAvailable, requireAuth, WRITE_ROLES, requireRole, } from '../../../route-helpers.js';
|
|
3
|
+
import { db } from './shared.js';
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// SEO Operations Center — audit engine, issues, overview, content records
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
export function seoIssueToApi(i) {
|
|
8
|
+
return {
|
|
9
|
+
id: i.id,
|
|
10
|
+
auditRunId: i.auditRunId ?? null,
|
|
11
|
+
entityType: i.entityType ?? null,
|
|
12
|
+
entityId: i.entityId ?? null,
|
|
13
|
+
entityTitle: i.entityTitle ?? null,
|
|
14
|
+
url: i.url ?? null,
|
|
15
|
+
severity: i.severity,
|
|
16
|
+
category: i.category,
|
|
17
|
+
type: i.type,
|
|
18
|
+
title: i.title,
|
|
19
|
+
description: i.description ?? null,
|
|
20
|
+
impact: i.impact ?? null,
|
|
21
|
+
recommendation: i.recommendation ?? null,
|
|
22
|
+
status: i.status,
|
|
23
|
+
fixable: i.fixable ?? false,
|
|
24
|
+
fixActionType: i.fixActionType ?? null,
|
|
25
|
+
relatedEntityIds: Array.isArray(i.relatedEntityIds) ? i.relatedEntityIds : [],
|
|
26
|
+
source: i.source ?? 'audit',
|
|
27
|
+
firstDetectedAt: i.firstDetectedAt ?? null,
|
|
28
|
+
lastDetectedAt: i.lastDetectedAt ?? null,
|
|
29
|
+
resolvedAt: i.resolvedAt ?? null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export async function setIssueStatus(request, issueId, status) {
|
|
33
|
+
const auth = await requireAuth(request);
|
|
34
|
+
if (auth.error)
|
|
35
|
+
return auth.error;
|
|
36
|
+
const roleErr = requireRole(auth.session.role, WRITE_ROLES);
|
|
37
|
+
if (roleErr)
|
|
38
|
+
return roleErr;
|
|
39
|
+
if (!hasModel(db(), 'seoIssue'))
|
|
40
|
+
return modelNotAvailable('SeoIssue');
|
|
41
|
+
const existing = await db().seoIssue.findUnique({ where: { id: issueId } });
|
|
42
|
+
if (!existing)
|
|
43
|
+
return errorResponse('Issue not found', 404);
|
|
44
|
+
const issue = await db().seoIssue.update({
|
|
45
|
+
where: { id: issueId },
|
|
46
|
+
data: {
|
|
47
|
+
status,
|
|
48
|
+
resolvedAt: status === 'resolved' ? new Date() : null,
|
|
49
|
+
resolvedById: status === 'resolved' ? auth.session.userId : null,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
try {
|
|
53
|
+
await logEvent({
|
|
54
|
+
event: 'settings_changed',
|
|
55
|
+
userId: auth.session.userId,
|
|
56
|
+
details: { action: 'seo_issue_status', issueId, status },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
/* noop */
|
|
61
|
+
}
|
|
62
|
+
return json({ data: seoIssueToApi(issue) });
|
|
63
|
+
}
|
|
64
|
+
/** Load the audit entity + collection for an issue's linked document. */
|
|
65
|
+
export async function loadIssueFixContext(issue) {
|
|
66
|
+
const { loadIssueFixContext: load } = await import('../../../../seo/issue-fix-apply.js');
|
|
67
|
+
const loaded = await load(db(), issue);
|
|
68
|
+
if (!loaded.ok)
|
|
69
|
+
return { error: errorResponse(loaded.message, loaded.status) };
|
|
70
|
+
return loaded.ctx;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=issue-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue-helpers.js","sourceRoot":"","sources":["../../../../../src/api/routes/seo/handlers/issue-helpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function postSeoIssuesBatchContentBriefs(request: Request): Promise<Response>;
|
|
2
|
+
export declare function getSeoIssueContentBrief(request: Request, params: Record<string, string>): Promise<Response>;
|
|
3
|
+
export declare function postSeoIssueContentBrief(request: Request, params: Record<string, string>): Promise<Response>;
|
|
4
|
+
export declare function getSeoIssueContentResearch(request: Request, params: Record<string, string>): Promise<Response>;
|
|
5
|
+
export declare function postSeoIssueContentResearch(request: Request, params: Record<string, string>): Promise<Response>;
|
|
6
|
+
export declare function postSeoIssueResearchAndBrief(request: Request, params: Record<string, string>): Promise<Response>;
|
|
7
|
+
//# sourceMappingURL=issues-briefs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issues-briefs.d.ts","sourceRoot":"","sources":["../../../../../src/api/routes/seo/handlers/issues-briefs.ts"],"names":[],"mappings":"AAkBA,wBAAsB,+BAA+B,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CA4GzF;AAED,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,OAAO,CAAC,QAAQ,CAAC,CA0CnB;AAED,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,OAAO,CAAC,QAAQ,CAAC,CAuDnB;AAED,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,OAAO,CAAC,QAAQ,CAAC,CA0CnB;AAED,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,OAAO,CAAC,QAAQ,CAAC,CAgEnB;AAED,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,OAAO,CAAC,QAAQ,CAAC,CAwFnB"}
|