@consilioweb/payload-seo-analyzer 1.11.0 → 1.12.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/README.md +1 -0
- package/dist/client.cjs +180 -43
- package/dist/client.js +180 -43
- package/dist/index.cjs +290 -82
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +290 -82
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1705,6 +1705,7 @@ async function buildAuditCache(payload, collections, globals, seoConfig) {
|
|
|
1705
1705
|
});
|
|
1706
1706
|
for (const doc of result.docs) {
|
|
1707
1707
|
if (ignoredSlugs.includes(doc.slug)) continue;
|
|
1708
|
+
if (doc._status === "draft") continue;
|
|
1708
1709
|
if (allResults.length >= MAX_DOCS2) {
|
|
1709
1710
|
capped = true;
|
|
1710
1711
|
break collectionsLoop;
|
|
@@ -2680,6 +2681,9 @@ function createRedirectsHandler(redirectsCollection) {
|
|
|
2680
2681
|
return Response.json({ error: "Missing id or ids" }, { status: 400 });
|
|
2681
2682
|
}
|
|
2682
2683
|
if (method === "PATCH") {
|
|
2684
|
+
if (!isAdmin3(req.user)) {
|
|
2685
|
+
return Response.json({ error: "Admin access required" }, { status: 403 });
|
|
2686
|
+
}
|
|
2683
2687
|
const patchBody = await parseJsonBody(req);
|
|
2684
2688
|
const id = typeof patchBody.id === "string" ? patchBody.id.trim() : void 0;
|
|
2685
2689
|
const from = typeof patchBody.from === "string" ? patchBody.from.trim() : void 0;
|
|
@@ -3021,6 +3025,77 @@ function sanitizeSuggestions(s, currentFocusKeyword) {
|
|
|
3021
3025
|
const rationale = s.rationale.map((r) => r.trim()).filter(Boolean).slice(0, RATIONALE_MAX_ITEMS).map((r) => r.length > RATIONALE_ITEM_MAX ? `${r.slice(0, RATIONALE_ITEM_MAX - 1)}\u2026` : r);
|
|
3022
3026
|
return { metaTitle, metaDescription, focusKeyword, rationale };
|
|
3023
3027
|
}
|
|
3028
|
+
async function optimizeDocMeta(payload, opts) {
|
|
3029
|
+
const { collection, id, mergedConfig } = opts;
|
|
3030
|
+
let doc;
|
|
3031
|
+
try {
|
|
3032
|
+
doc = await payload.findByID({ collection, id, depth: 1, overrideAccess: true });
|
|
3033
|
+
} catch {
|
|
3034
|
+
return { ok: false, error: `Document not found: ${collection}/${id}`, status: 404 };
|
|
3035
|
+
}
|
|
3036
|
+
const seoInput = buildSeoInputFromDoc(doc, collection);
|
|
3037
|
+
const analysis = analyzeSeo(seoInput, mergedConfig);
|
|
3038
|
+
const metaGroups = /* @__PURE__ */ new Set(["title", "meta-description", "content", "url", "headings"]);
|
|
3039
|
+
const issues = analysis.checks.filter((c) => (c.status === "fail" || c.status === "warning") && metaGroups.has(c.group)).map((c) => ({ label: c.label, message: c.message })).slice(0, 12);
|
|
3040
|
+
const pageTitle = doc.title || "";
|
|
3041
|
+
const slug = doc.slug || "";
|
|
3042
|
+
const currentFocusKeyword = doc.focusKeyword || "";
|
|
3043
|
+
const currentMetaTitle = seoInput.metaTitle || "";
|
|
3044
|
+
const currentMetaDescription = seoInput.metaDescription || "";
|
|
3045
|
+
const content = extractDocContent(doc).text;
|
|
3046
|
+
const apiKey = opts.apiKey;
|
|
3047
|
+
const model = opts.model || DEFAULT_MODEL;
|
|
3048
|
+
let suggestions;
|
|
3049
|
+
let method;
|
|
3050
|
+
const heuristicFallback = () => ({
|
|
3051
|
+
metaTitle: generateMetaTitle(pageTitle, currentFocusKeyword, slug),
|
|
3052
|
+
metaDescription: generateMetaDescription(content, currentFocusKeyword, slug),
|
|
3053
|
+
focusKeyword: currentFocusKeyword,
|
|
3054
|
+
rationale: []
|
|
3055
|
+
});
|
|
3056
|
+
if (apiKey) {
|
|
3057
|
+
try {
|
|
3058
|
+
const aiResult = await callClaudeOptimize(apiKey, model, {
|
|
3059
|
+
pageTitle,
|
|
3060
|
+
slug,
|
|
3061
|
+
focusKeyword: currentFocusKeyword,
|
|
3062
|
+
currentMetaTitle,
|
|
3063
|
+
currentMetaDescription,
|
|
3064
|
+
issues,
|
|
3065
|
+
content
|
|
3066
|
+
});
|
|
3067
|
+
if (aiResult) {
|
|
3068
|
+
suggestions = aiResult;
|
|
3069
|
+
method = "ai";
|
|
3070
|
+
} else {
|
|
3071
|
+
suggestions = heuristicFallback();
|
|
3072
|
+
method = "heuristic";
|
|
3073
|
+
}
|
|
3074
|
+
} catch (error) {
|
|
3075
|
+
payload.logger.error(`[seo] ai-optimize Claude API error: ${error instanceof Error ? error.message : "unknown"}`);
|
|
3076
|
+
suggestions = heuristicFallback();
|
|
3077
|
+
method = "heuristic";
|
|
3078
|
+
}
|
|
3079
|
+
} else {
|
|
3080
|
+
suggestions = heuristicFallback();
|
|
3081
|
+
method = "heuristic";
|
|
3082
|
+
}
|
|
3083
|
+
const sanitized = sanitizeSuggestions(suggestions, currentFocusKeyword);
|
|
3084
|
+
return {
|
|
3085
|
+
ok: true,
|
|
3086
|
+
title: pageTitle,
|
|
3087
|
+
method,
|
|
3088
|
+
...method === "ai" ? { model } : {},
|
|
3089
|
+
score: analysis.score,
|
|
3090
|
+
current: {
|
|
3091
|
+
metaTitle: currentMetaTitle,
|
|
3092
|
+
metaDescription: currentMetaDescription,
|
|
3093
|
+
focusKeyword: currentFocusKeyword
|
|
3094
|
+
},
|
|
3095
|
+
suggestions: sanitized,
|
|
3096
|
+
issues
|
|
3097
|
+
};
|
|
3098
|
+
}
|
|
3024
3099
|
function createAiOptimizeHandler(targetCollections, seoConfig, localeMapping) {
|
|
3025
3100
|
return async (req) => {
|
|
3026
3101
|
try {
|
|
@@ -3036,83 +3111,27 @@ function createAiOptimizeHandler(targetCollections, seoConfig, localeMapping) {
|
|
|
3036
3111
|
if (targetCollections && !targetCollections.includes(collection)) {
|
|
3037
3112
|
return Response.json({ error: "Collection not allowed" }, { status: 403 });
|
|
3038
3113
|
}
|
|
3039
|
-
let doc;
|
|
3040
|
-
try {
|
|
3041
|
-
const result = await req.payload.findByID({
|
|
3042
|
-
collection,
|
|
3043
|
-
id,
|
|
3044
|
-
depth: 1,
|
|
3045
|
-
overrideAccess: true
|
|
3046
|
-
});
|
|
3047
|
-
doc = result;
|
|
3048
|
-
} catch {
|
|
3049
|
-
return Response.json({ error: `Document not found: ${collection}/${id}` }, { status: 404 });
|
|
3050
|
-
}
|
|
3051
3114
|
const { config: mergedConfig } = await loadMergedConfig(req.payload, seoConfig, {
|
|
3052
3115
|
reqLocale: req.locale,
|
|
3053
3116
|
localeMapping
|
|
3054
3117
|
});
|
|
3055
|
-
const
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
const currentFocusKeyword = doc.focusKeyword || "";
|
|
3062
|
-
const currentMetaTitle = seoInput.metaTitle || "";
|
|
3063
|
-
const currentMetaDescription = seoInput.metaDescription || "";
|
|
3064
|
-
const content = extractDocContent(doc).text;
|
|
3065
|
-
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
3066
|
-
const model = process.env.SEO_AI_MODEL || DEFAULT_MODEL;
|
|
3067
|
-
let suggestions;
|
|
3068
|
-
let method;
|
|
3069
|
-
const heuristicFallback = () => ({
|
|
3070
|
-
metaTitle: generateMetaTitle(pageTitle, currentFocusKeyword, slug),
|
|
3071
|
-
metaDescription: generateMetaDescription(content, currentFocusKeyword, slug),
|
|
3072
|
-
focusKeyword: currentFocusKeyword,
|
|
3073
|
-
rationale: []
|
|
3118
|
+
const r = await optimizeDocMeta(req.payload, {
|
|
3119
|
+
collection,
|
|
3120
|
+
id,
|
|
3121
|
+
mergedConfig,
|
|
3122
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
3123
|
+
model: process.env.SEO_AI_MODEL
|
|
3074
3124
|
});
|
|
3075
|
-
if (
|
|
3076
|
-
|
|
3077
|
-
const aiResult = await callClaudeOptimize(apiKey, model, {
|
|
3078
|
-
pageTitle,
|
|
3079
|
-
slug,
|
|
3080
|
-
focusKeyword: currentFocusKeyword,
|
|
3081
|
-
currentMetaTitle,
|
|
3082
|
-
currentMetaDescription,
|
|
3083
|
-
issues,
|
|
3084
|
-
content
|
|
3085
|
-
});
|
|
3086
|
-
if (aiResult) {
|
|
3087
|
-
suggestions = aiResult;
|
|
3088
|
-
method = "ai";
|
|
3089
|
-
} else {
|
|
3090
|
-
suggestions = heuristicFallback();
|
|
3091
|
-
method = "heuristic";
|
|
3092
|
-
}
|
|
3093
|
-
} catch (error) {
|
|
3094
|
-
req.payload.logger.error(
|
|
3095
|
-
`[seo] ai-optimize Claude API error: ${error instanceof Error ? error.message : "unknown"}`
|
|
3096
|
-
);
|
|
3097
|
-
suggestions = heuristicFallback();
|
|
3098
|
-
method = "heuristic";
|
|
3099
|
-
}
|
|
3100
|
-
} else {
|
|
3101
|
-
suggestions = heuristicFallback();
|
|
3102
|
-
method = "heuristic";
|
|
3125
|
+
if (!r.ok) {
|
|
3126
|
+
return Response.json({ error: r.error }, { status: r.status || 500 });
|
|
3103
3127
|
}
|
|
3104
|
-
const sanitized = sanitizeSuggestions(suggestions, currentFocusKeyword);
|
|
3105
3128
|
return Response.json({
|
|
3106
|
-
method,
|
|
3107
|
-
...method === "ai" ? { model } : {},
|
|
3108
|
-
score:
|
|
3109
|
-
current:
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
focusKeyword: currentFocusKeyword
|
|
3113
|
-
},
|
|
3114
|
-
suggestions: sanitized,
|
|
3115
|
-
issues
|
|
3129
|
+
method: r.method,
|
|
3130
|
+
...r.method === "ai" ? { model: r.model } : {},
|
|
3131
|
+
score: r.score,
|
|
3132
|
+
current: r.current,
|
|
3133
|
+
suggestions: r.suggestions,
|
|
3134
|
+
issues: r.issues
|
|
3116
3135
|
});
|
|
3117
3136
|
} catch (error) {
|
|
3118
3137
|
const message = error instanceof Error ? error.message : "Internal server error";
|
|
@@ -3567,6 +3586,177 @@ function createAiContentBriefHandler(targetCollections, seoConfig) {
|
|
|
3567
3586
|
};
|
|
3568
3587
|
}
|
|
3569
3588
|
|
|
3589
|
+
// src/endpoints/aiOptimizeBulk.ts
|
|
3590
|
+
var TITLE_MAX = 70;
|
|
3591
|
+
var DESC_MAX = 160;
|
|
3592
|
+
var KEYWORD_MAX2 = 60;
|
|
3593
|
+
function isAdmin5(user) {
|
|
3594
|
+
if (!user) return false;
|
|
3595
|
+
if (user.role === "admin") return true;
|
|
3596
|
+
if (Array.isArray(user.roles) && user.roles.includes("admin")) return true;
|
|
3597
|
+
return false;
|
|
3598
|
+
}
|
|
3599
|
+
function createAiOptimizeBulkHandler(targetCollections, seoConfig, localeMapping) {
|
|
3600
|
+
return async (req) => {
|
|
3601
|
+
try {
|
|
3602
|
+
if (!isAdmin5(req.user)) return Response.json({ error: "Forbidden" }, { status: 403 });
|
|
3603
|
+
const body = await parseJsonBody(req);
|
|
3604
|
+
const rawIds = Array.isArray(body.ids) ? body.ids.map(String) : [];
|
|
3605
|
+
const defaultCollection = typeof body.collection === "string" ? body.collection : void 0;
|
|
3606
|
+
const apply = body.apply === true;
|
|
3607
|
+
const MAX = 100;
|
|
3608
|
+
const limit = Math.min(MAX, Math.max(1, parseInt(String(body.limit ?? 50), 10) || 50));
|
|
3609
|
+
const corrections = Array.isArray(body.corrections) ? body.corrections : null;
|
|
3610
|
+
if (apply && corrections && corrections.length > 0) {
|
|
3611
|
+
let applied2 = 0;
|
|
3612
|
+
let i2 = 0;
|
|
3613
|
+
const rows = [];
|
|
3614
|
+
for (const c of corrections.slice(0, MAX)) {
|
|
3615
|
+
i2++;
|
|
3616
|
+
const collection = typeof c.collection === "string" ? c.collection : defaultCollection;
|
|
3617
|
+
const id = c.id != null ? String(c.id) : void 0;
|
|
3618
|
+
if (!collection || !id || collection.startsWith("global:")) continue;
|
|
3619
|
+
if (targetCollections && !targetCollections.includes(collection)) continue;
|
|
3620
|
+
const metaTitle = typeof c.metaTitle === "string" ? truncateWords(c.metaTitle.trim(), TITLE_MAX) : "";
|
|
3621
|
+
const metaDescription = typeof c.metaDescription === "string" ? truncateWords(c.metaDescription.trim(), DESC_MAX) : "";
|
|
3622
|
+
const focusKeyword = typeof c.focusKeyword === "string" ? c.focusKeyword.trim().slice(0, KEYWORD_MAX2) : "";
|
|
3623
|
+
const patch = {};
|
|
3624
|
+
if (metaTitle || metaDescription) patch.meta = { title: metaTitle, description: metaDescription };
|
|
3625
|
+
if (focusKeyword) patch.focusKeyword = focusKeyword;
|
|
3626
|
+
try {
|
|
3627
|
+
if (Object.keys(patch).length > 0) {
|
|
3628
|
+
await req.payload.update({ collection, id, data: patch, overrideAccess: true });
|
|
3629
|
+
applied2++;
|
|
3630
|
+
rows.push({ collection, id, applied: true });
|
|
3631
|
+
}
|
|
3632
|
+
} catch (e) {
|
|
3633
|
+
rows.push({ collection, id, applied: false, error: e instanceof Error ? e.message : "error" });
|
|
3634
|
+
}
|
|
3635
|
+
if (i2 % 5 === 0) await new Promise((resolve) => setImmediate(resolve));
|
|
3636
|
+
}
|
|
3637
|
+
return Response.json(
|
|
3638
|
+
{ processed: rows.length, applied: applied2, mode: "corrections", results: rows },
|
|
3639
|
+
{ headers: { "Cache-Control": "no-store" } }
|
|
3640
|
+
);
|
|
3641
|
+
}
|
|
3642
|
+
if (rawIds.length === 0) {
|
|
3643
|
+
return Response.json({ error: 'Provide a non-empty "ids" array' }, { status: 400 });
|
|
3644
|
+
}
|
|
3645
|
+
const targets = [];
|
|
3646
|
+
for (const raw of rawIds) {
|
|
3647
|
+
let collection;
|
|
3648
|
+
let id;
|
|
3649
|
+
if (raw.includes("::")) {
|
|
3650
|
+
const [c, i2] = raw.split("::");
|
|
3651
|
+
collection = c;
|
|
3652
|
+
id = i2;
|
|
3653
|
+
} else {
|
|
3654
|
+
collection = defaultCollection;
|
|
3655
|
+
id = raw;
|
|
3656
|
+
}
|
|
3657
|
+
if (!collection || !id) continue;
|
|
3658
|
+
if (collection.startsWith("global:")) continue;
|
|
3659
|
+
if (targetCollections && !targetCollections.includes(collection)) continue;
|
|
3660
|
+
targets.push({ collection, id });
|
|
3661
|
+
}
|
|
3662
|
+
const capped = targets.length > limit;
|
|
3663
|
+
const slice = targets.slice(0, limit);
|
|
3664
|
+
const { config: mergedConfig } = await loadMergedConfig(req.payload, seoConfig, {
|
|
3665
|
+
reqLocale: req.locale,
|
|
3666
|
+
localeMapping
|
|
3667
|
+
});
|
|
3668
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
3669
|
+
const model = process.env.SEO_AI_MODEL;
|
|
3670
|
+
const results = [];
|
|
3671
|
+
let applied = 0;
|
|
3672
|
+
let i = 0;
|
|
3673
|
+
for (const target of slice) {
|
|
3674
|
+
i++;
|
|
3675
|
+
try {
|
|
3676
|
+
const r = await optimizeDocMeta(req.payload, {
|
|
3677
|
+
collection: target.collection,
|
|
3678
|
+
id: target.id,
|
|
3679
|
+
mergedConfig,
|
|
3680
|
+
apiKey,
|
|
3681
|
+
model
|
|
3682
|
+
});
|
|
3683
|
+
if (!r.ok || !r.current || !r.suggestions) {
|
|
3684
|
+
results.push({
|
|
3685
|
+
collection: target.collection,
|
|
3686
|
+
id: target.id,
|
|
3687
|
+
title: "",
|
|
3688
|
+
before: { metaTitle: "", metaDescription: "", focusKeyword: "" },
|
|
3689
|
+
after: { metaTitle: "", metaDescription: "", focusKeyword: "" },
|
|
3690
|
+
changed: false,
|
|
3691
|
+
applied: false,
|
|
3692
|
+
error: r.error || "optimize_failed"
|
|
3693
|
+
});
|
|
3694
|
+
continue;
|
|
3695
|
+
}
|
|
3696
|
+
const before = r.current;
|
|
3697
|
+
const after = {
|
|
3698
|
+
metaTitle: r.suggestions.metaTitle,
|
|
3699
|
+
metaDescription: r.suggestions.metaDescription,
|
|
3700
|
+
focusKeyword: r.suggestions.focusKeyword
|
|
3701
|
+
};
|
|
3702
|
+
const changed = after.metaTitle !== before.metaTitle || after.metaDescription !== before.metaDescription || after.focusKeyword !== before.focusKeyword;
|
|
3703
|
+
let didApply = false;
|
|
3704
|
+
if (apply && changed) {
|
|
3705
|
+
const patch = {};
|
|
3706
|
+
if (after.metaTitle || after.metaDescription) {
|
|
3707
|
+
patch.meta = { title: after.metaTitle, description: after.metaDescription };
|
|
3708
|
+
}
|
|
3709
|
+
if (after.focusKeyword && after.focusKeyword !== before.focusKeyword) {
|
|
3710
|
+
patch.focusKeyword = after.focusKeyword;
|
|
3711
|
+
}
|
|
3712
|
+
if (Object.keys(patch).length > 0) {
|
|
3713
|
+
await req.payload.update({ collection: target.collection, id: target.id, data: patch, overrideAccess: true });
|
|
3714
|
+
didApply = true;
|
|
3715
|
+
applied++;
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
results.push({
|
|
3719
|
+
collection: target.collection,
|
|
3720
|
+
id: target.id,
|
|
3721
|
+
title: r.title || "",
|
|
3722
|
+
before,
|
|
3723
|
+
after,
|
|
3724
|
+
changed,
|
|
3725
|
+
applied: didApply,
|
|
3726
|
+
method: r.method
|
|
3727
|
+
});
|
|
3728
|
+
} catch (e) {
|
|
3729
|
+
results.push({
|
|
3730
|
+
collection: target.collection,
|
|
3731
|
+
id: target.id,
|
|
3732
|
+
title: "",
|
|
3733
|
+
before: { metaTitle: "", metaDescription: "", focusKeyword: "" },
|
|
3734
|
+
after: { metaTitle: "", metaDescription: "", focusKeyword: "" },
|
|
3735
|
+
changed: false,
|
|
3736
|
+
applied: false,
|
|
3737
|
+
error: e instanceof Error ? e.message : "error"
|
|
3738
|
+
});
|
|
3739
|
+
}
|
|
3740
|
+
if (i % 3 === 0) await new Promise((resolve) => setImmediate(resolve));
|
|
3741
|
+
}
|
|
3742
|
+
return Response.json(
|
|
3743
|
+
{
|
|
3744
|
+
processed: results.length,
|
|
3745
|
+
changedCount: results.filter((r) => r.changed).length,
|
|
3746
|
+
applied,
|
|
3747
|
+
capped,
|
|
3748
|
+
results
|
|
3749
|
+
},
|
|
3750
|
+
{ headers: { "Cache-Control": "no-store" } }
|
|
3751
|
+
);
|
|
3752
|
+
} catch (error) {
|
|
3753
|
+
const message = error instanceof Error ? error.message : "Internal server error";
|
|
3754
|
+
req.payload.logger.error(`[seo] ai-optimize-bulk error: ${message}`);
|
|
3755
|
+
return Response.json({ error: message }, { status: 500 });
|
|
3756
|
+
}
|
|
3757
|
+
};
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3570
3760
|
// src/endpoints/cannibalization.ts
|
|
3571
3761
|
function canonicalIntent(keyword) {
|
|
3572
3762
|
return keyword.toLowerCase().normalize("NFD").replace(/\p{Diacritic}/gu, "").replace(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/).filter(Boolean).sort().join(" ");
|
|
@@ -4151,7 +4341,7 @@ function getDateThreshold(period) {
|
|
|
4151
4341
|
return new Date(now.getTime() - 30 * 24 * 60 * 60 * 1e3);
|
|
4152
4342
|
}
|
|
4153
4343
|
}
|
|
4154
|
-
function
|
|
4344
|
+
function isAdmin6(user) {
|
|
4155
4345
|
if (!user) return false;
|
|
4156
4346
|
if (user.role === "admin") return true;
|
|
4157
4347
|
if (Array.isArray(user.roles) && user.roles.includes("admin")) return true;
|
|
@@ -4256,7 +4446,7 @@ function createPerformanceHandler() {
|
|
|
4256
4446
|
});
|
|
4257
4447
|
}
|
|
4258
4448
|
if (method === "POST") {
|
|
4259
|
-
if (!
|
|
4449
|
+
if (!isAdmin6(req.user)) {
|
|
4260
4450
|
return Response.json({ error: "Admin access required" }, { status: 403 });
|
|
4261
4451
|
}
|
|
4262
4452
|
const body = await parseJsonBody(req);
|
|
@@ -5970,7 +6160,7 @@ function createAiRewriteHandler(targetCollections) {
|
|
|
5970
6160
|
}
|
|
5971
6161
|
|
|
5972
6162
|
// src/endpoints/robots.ts
|
|
5973
|
-
function
|
|
6163
|
+
function isAdmin7(user) {
|
|
5974
6164
|
if (!user) return false;
|
|
5975
6165
|
if (user.role === "admin") return true;
|
|
5976
6166
|
if (Array.isArray(user.roles) && user.roles.includes("admin")) return true;
|
|
@@ -6019,7 +6209,7 @@ function createRobotsUpdateHandler() {
|
|
|
6019
6209
|
if (!req.user) {
|
|
6020
6210
|
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
|
6021
6211
|
}
|
|
6022
|
-
if (!
|
|
6212
|
+
if (!isAdmin7(req.user)) {
|
|
6023
6213
|
return Response.json({ error: "Admin access required" }, { status: 403 });
|
|
6024
6214
|
}
|
|
6025
6215
|
const body = await parseJsonBody(req);
|
|
@@ -7249,7 +7439,7 @@ function getClientIp(req) {
|
|
|
7249
7439
|
|
|
7250
7440
|
// src/endpoints/seoLogs.ts
|
|
7251
7441
|
var VALID_LOG_TYPES = ["404", "redirect", "error"];
|
|
7252
|
-
function
|
|
7442
|
+
function isAdmin8(user) {
|
|
7253
7443
|
if (!user) return false;
|
|
7254
7444
|
if (user.role === "admin") return true;
|
|
7255
7445
|
if (Array.isArray(user.roles) && user.roles.includes("admin")) return true;
|
|
@@ -7346,7 +7536,7 @@ function createSeoLogsHandler(seoLogsSecret) {
|
|
|
7346
7536
|
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
|
7347
7537
|
}
|
|
7348
7538
|
if (method === "DELETE") {
|
|
7349
|
-
if (!
|
|
7539
|
+
if (!isAdmin8(req.user)) {
|
|
7350
7540
|
return Response.json({ error: "Admin access required" }, { status: 403 });
|
|
7351
7541
|
}
|
|
7352
7542
|
try {
|
|
@@ -7523,6 +7713,7 @@ async function doWarmUp(payload, collections = ["pages", "posts"], globals = [])
|
|
|
7523
7713
|
}
|
|
7524
7714
|
}
|
|
7525
7715
|
function startCacheWarmUp(payload, _basePath, globals = [], collections = ["pages", "posts"]) {
|
|
7716
|
+
stopCacheWarmUp();
|
|
7526
7717
|
setTimeout(() => {
|
|
7527
7718
|
void doWarmUp(payload, collections, globals);
|
|
7528
7719
|
}, STARTUP_DELAY);
|
|
@@ -7564,6 +7755,7 @@ async function doSnapshot(payload, basePath, seoConfig) {
|
|
|
7564
7755
|
}
|
|
7565
7756
|
}
|
|
7566
7757
|
function startRankTracker(payload, basePath, seoConfig) {
|
|
7758
|
+
stopRankTracker();
|
|
7567
7759
|
setTimeout(() => {
|
|
7568
7760
|
void doSnapshot(payload, basePath, seoConfig);
|
|
7569
7761
|
}, STARTUP_DELAY2);
|
|
@@ -7586,7 +7778,7 @@ function stopRankTracker() {
|
|
|
7586
7778
|
}
|
|
7587
7779
|
|
|
7588
7780
|
// src/endpoints/alerts.ts
|
|
7589
|
-
function
|
|
7781
|
+
function isAdmin9(user) {
|
|
7590
7782
|
if (!user) return false;
|
|
7591
7783
|
if (user.role === "admin") return true;
|
|
7592
7784
|
if (Array.isArray(user.roles) && user.roles.includes("admin")) return true;
|
|
@@ -7752,7 +7944,7 @@ async function deliverAlertDigest(payload, digest, cfg, siteUrl) {
|
|
|
7752
7944
|
function createAlertsDigestHandler() {
|
|
7753
7945
|
return async (req) => {
|
|
7754
7946
|
try {
|
|
7755
|
-
if (!
|
|
7947
|
+
if (!isAdmin9(req.user)) return Response.json({ error: "Forbidden" }, { status: 403 });
|
|
7756
7948
|
const cfg = getAlertConfig();
|
|
7757
7949
|
const digest = await buildAlertDigest(req.payload, cfg);
|
|
7758
7950
|
return Response.json(
|
|
@@ -7778,7 +7970,7 @@ function createAlertsDigestHandler() {
|
|
|
7778
7970
|
function createAlertsRunHandler(siteUrl) {
|
|
7779
7971
|
return async (req) => {
|
|
7780
7972
|
try {
|
|
7781
|
-
if (!
|
|
7973
|
+
if (!isAdmin9(req.user)) return Response.json({ error: "Forbidden" }, { status: 403 });
|
|
7782
7974
|
const cfg = getAlertConfig();
|
|
7783
7975
|
const digest = await buildAlertDigest(req.payload, cfg);
|
|
7784
7976
|
const delivery = await deliverAlertDigest(req.payload, digest, cfg, siteUrl);
|
|
@@ -7813,6 +8005,7 @@ async function runDigest(payload, siteUrl) {
|
|
|
7813
8005
|
}
|
|
7814
8006
|
}
|
|
7815
8007
|
function startAlertsScheduler(payload, siteUrl) {
|
|
8008
|
+
stopAlertsScheduler();
|
|
7816
8009
|
const intervalHours = Math.max(1, parseInt(process.env.SEO_ALERT_INTERVAL_HOURS || "24", 10) || 24);
|
|
7817
8010
|
const intervalMs = intervalHours * 60 * 60 * 1e3;
|
|
7818
8011
|
setTimeout(() => {
|
|
@@ -8828,8 +9021,15 @@ var fr = {
|
|
|
8828
9021
|
markCornerstone: "Marquer pilier",
|
|
8829
9022
|
unmarkCornerstone: "D\xE9marquer pilier",
|
|
8830
9023
|
bulkOptimizeMeta: "Optimiser m\xE9ta (IA)",
|
|
8831
|
-
bulkOptimizing: "
|
|
9024
|
+
bulkOptimizing: "Analyse\u2026",
|
|
8832
9025
|
bulkConfirm: "Confirmer ?",
|
|
9026
|
+
bulkPreviewTitle: "Corrections m\xE9ta propos\xE9es",
|
|
9027
|
+
bulkApply: "Appliquer",
|
|
9028
|
+
bulkApplying: "Application\u2026",
|
|
9029
|
+
bulkCancel: "Annuler",
|
|
9030
|
+
bulkExport: "Exporter CSV",
|
|
9031
|
+
bulkNoChanges: "Aucune correction n\xE9cessaire sur la s\xE9lection.",
|
|
9032
|
+
bulkCappedNote: "limite atteinte (affine la s\xE9lection)",
|
|
8833
9033
|
searchPlaceholder: "Rechercher (titre, slug, keyword)...",
|
|
8834
9034
|
allCollections: "Toutes les collections",
|
|
8835
9035
|
allScores: "Tous les scores",
|
|
@@ -9423,8 +9623,15 @@ var en = {
|
|
|
9423
9623
|
markCornerstone: "Mark as cornerstone",
|
|
9424
9624
|
unmarkCornerstone: "Unmark cornerstone",
|
|
9425
9625
|
bulkOptimizeMeta: "Optimize meta (AI)",
|
|
9426
|
-
bulkOptimizing: "
|
|
9626
|
+
bulkOptimizing: "Analyzing\u2026",
|
|
9427
9627
|
bulkConfirm: "Confirm?",
|
|
9628
|
+
bulkPreviewTitle: "Proposed meta corrections",
|
|
9629
|
+
bulkApply: "Apply",
|
|
9630
|
+
bulkApplying: "Applying\u2026",
|
|
9631
|
+
bulkCancel: "Cancel",
|
|
9632
|
+
bulkExport: "Export CSV",
|
|
9633
|
+
bulkNoChanges: "No corrections needed on the selection.",
|
|
9634
|
+
bulkCappedNote: "limit reached (narrow the selection)",
|
|
9428
9635
|
searchPlaceholder: "Search (title, slug, keyword)...",
|
|
9429
9636
|
allCollections: "All collections",
|
|
9430
9637
|
allScores: "All scores",
|
|
@@ -10277,7 +10484,8 @@ var seoAnalyzerPlugin = (pluginConfig = {}) => (incomingConfig) => {
|
|
|
10277
10484
|
{ path: `${basePath}/ai-optimize`, method: "post", handler: createAiOptimizeHandler(targetCollections, seoConfig) },
|
|
10278
10485
|
{ path: `${basePath}/alt-text-audit`, method: "get", handler: createAltTextAuditHandler(uploadsCollection) },
|
|
10279
10486
|
{ path: `${basePath}/ai-alt-text`, method: "post", handler: withRateLimit(createAiAltTextHandler(uploadsCollection, seoConfig)) },
|
|
10280
|
-
{ path: `${basePath}/ai-content-brief`, method: "post", handler: withRateLimit(createAiContentBriefHandler(targetCollections, seoConfig)) }
|
|
10487
|
+
{ path: `${basePath}/ai-content-brief`, method: "post", handler: withRateLimit(createAiContentBriefHandler(targetCollections, seoConfig)) },
|
|
10488
|
+
{ path: `${basePath}/ai-optimize-bulk`, method: "post", handler: withRateLimit(createAiOptimizeBulkHandler(targetCollections, seoConfig)) }
|
|
10281
10489
|
);
|
|
10282
10490
|
}
|
|
10283
10491
|
if (features.cannibalization) {
|
package/dist/index.d.cts
CHANGED
|
@@ -470,6 +470,13 @@ interface DashboardTranslations {
|
|
|
470
470
|
bulkOptimizeMeta: string;
|
|
471
471
|
bulkOptimizing: string;
|
|
472
472
|
bulkConfirm: string;
|
|
473
|
+
bulkPreviewTitle: string;
|
|
474
|
+
bulkApply: string;
|
|
475
|
+
bulkApplying: string;
|
|
476
|
+
bulkCancel: string;
|
|
477
|
+
bulkExport: string;
|
|
478
|
+
bulkNoChanges: string;
|
|
479
|
+
bulkCappedNote: string;
|
|
473
480
|
searchPlaceholder: string;
|
|
474
481
|
allCollections: string;
|
|
475
482
|
allScores: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -470,6 +470,13 @@ interface DashboardTranslations {
|
|
|
470
470
|
bulkOptimizeMeta: string;
|
|
471
471
|
bulkOptimizing: string;
|
|
472
472
|
bulkConfirm: string;
|
|
473
|
+
bulkPreviewTitle: string;
|
|
474
|
+
bulkApply: string;
|
|
475
|
+
bulkApplying: string;
|
|
476
|
+
bulkCancel: string;
|
|
477
|
+
bulkExport: string;
|
|
478
|
+
bulkNoChanges: string;
|
|
479
|
+
bulkCappedNote: string;
|
|
473
480
|
searchPlaceholder: string;
|
|
474
481
|
allCollections: string;
|
|
475
482
|
allScores: string;
|