@consilioweb/payload-seo-analyzer 1.13.0 → 1.15.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/client.cjs +320 -211
- package/dist/client.js +320 -211
- package/dist/index.cjs +100 -18
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +100 -18
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1683,9 +1683,9 @@ function analyzeDoc(doc, collection, seoConfig) {
|
|
|
1683
1683
|
};
|
|
1684
1684
|
}
|
|
1685
1685
|
var CACHE_KEY = "audit";
|
|
1686
|
-
var
|
|
1687
|
-
async function buildAuditCache(payload, collections, globals, seoConfig) {
|
|
1688
|
-
const { config: mergedConfig, ignoredSlugs } = await loadMergedConfig(payload, seoConfig);
|
|
1686
|
+
var auditBuildsInFlight = /* @__PURE__ */ new Set();
|
|
1687
|
+
async function buildAuditCache(payload, collections, globals, seoConfig, reqLocale) {
|
|
1688
|
+
const { config: mergedConfig, ignoredSlugs } = await loadMergedConfig(payload, seoConfig, { reqLocale });
|
|
1689
1689
|
const BATCH_SIZE = Math.min(100, Math.max(1, parseInt(process.env.SEO_AUDIT_BATCH_SIZE || "15", 10) || 15));
|
|
1690
1690
|
const MAX_DOCS2 = Math.max(1, parseInt(process.env.SEO_AUDIT_MAX_DOCS || "1500", 10) || 1500);
|
|
1691
1691
|
const allResults = [];
|
|
@@ -1793,15 +1793,15 @@ async function buildAuditCache(payload, collections, globals, seoConfig) {
|
|
|
1793
1793
|
};
|
|
1794
1794
|
return { enrichedResults, stats, capped };
|
|
1795
1795
|
}
|
|
1796
|
-
function ensureAuditBuild(payload, collections, globals, seoConfig) {
|
|
1797
|
-
if (
|
|
1798
|
-
|
|
1799
|
-
void buildAuditCache(payload, collections, globals, seoConfig).then((result) => {
|
|
1800
|
-
seoCache.set(
|
|
1796
|
+
function ensureAuditBuild(payload, collections, globals, seoConfig, cacheKey, reqLocale) {
|
|
1797
|
+
if (auditBuildsInFlight.has(cacheKey)) return;
|
|
1798
|
+
auditBuildsInFlight.add(cacheKey);
|
|
1799
|
+
void buildAuditCache(payload, collections, globals, seoConfig, reqLocale).then((result) => {
|
|
1800
|
+
seoCache.set(cacheKey, result);
|
|
1801
1801
|
}).catch((e) => {
|
|
1802
1802
|
payload.logger.error(`[seo] audit build failed: ${e instanceof Error ? e.message : "unknown"}`);
|
|
1803
1803
|
}).finally(() => {
|
|
1804
|
-
|
|
1804
|
+
auditBuildsInFlight.delete(cacheKey);
|
|
1805
1805
|
});
|
|
1806
1806
|
}
|
|
1807
1807
|
function createAuditHandler(collections, seoConfig, globals = []) {
|
|
@@ -1814,12 +1814,14 @@ function createAuditHandler(collections, seoConfig, globals = []) {
|
|
|
1814
1814
|
const page = Math.max(1, parseInt(url.searchParams.get("page") || "1", 10));
|
|
1815
1815
|
const limit = Math.min(500, Math.max(1, parseInt(url.searchParams.get("limit") || "300", 10)));
|
|
1816
1816
|
const noCache = url.searchParams.get("nocache") === "1";
|
|
1817
|
-
|
|
1818
|
-
|
|
1817
|
+
const reqLocale = typeof req.locale === "string" && req.locale ? req.locale : void 0;
|
|
1818
|
+
const cacheKey = reqLocale ? `${CACHE_KEY}:${reqLocale}` : CACHE_KEY;
|
|
1819
|
+
if (noCache && !auditBuildsInFlight.has(cacheKey)) {
|
|
1820
|
+
seoCache.invalidateKey(cacheKey);
|
|
1819
1821
|
}
|
|
1820
|
-
const cached = seoCache.get(
|
|
1822
|
+
const cached = seoCache.get(cacheKey);
|
|
1821
1823
|
if (!cached) {
|
|
1822
|
-
ensureAuditBuild(req.payload, collections, globals, seoConfig);
|
|
1824
|
+
ensureAuditBuild(req.payload, collections, globals, seoConfig, cacheKey, reqLocale);
|
|
1823
1825
|
return Response.json(
|
|
1824
1826
|
{ building: true, results: [], stats: null },
|
|
1825
1827
|
{ status: 202, headers: { "Cache-Control": "no-store" } }
|
|
@@ -7523,6 +7525,77 @@ function createCtrOpportunitiesHandler(basePath, targetCollections, seoConfig) {
|
|
|
7523
7525
|
};
|
|
7524
7526
|
}
|
|
7525
7527
|
|
|
7528
|
+
// src/endpoints/health.ts
|
|
7529
|
+
function isAdmin8(user) {
|
|
7530
|
+
if (!user) return false;
|
|
7531
|
+
if (user.role === "admin") return true;
|
|
7532
|
+
if (Array.isArray(user.roles) && user.roles.includes("admin")) return true;
|
|
7533
|
+
return false;
|
|
7534
|
+
}
|
|
7535
|
+
function createSeoHealthHandler(basePath, seoConfig) {
|
|
7536
|
+
return async (req) => {
|
|
7537
|
+
try {
|
|
7538
|
+
if (!isAdmin8(req.user)) return Response.json({ error: "Forbidden" }, { status: 403 });
|
|
7539
|
+
const config = {
|
|
7540
|
+
aiKey: !!process.env.ANTHROPIC_API_KEY,
|
|
7541
|
+
aiModel: process.env.SEO_AI_MODEL || "claude-opus-4-8",
|
|
7542
|
+
pageSpeedKey: !!(process.env.PAGESPEED_API_KEY || process.env.GOOGLE_PAGESPEED_API_KEY),
|
|
7543
|
+
gscConfigured: !!getGscOAuthConfig(basePath, seoConfig),
|
|
7544
|
+
gscEncryptionKey: !!process.env.SEO_GSC_ENCRYPTION_KEY,
|
|
7545
|
+
alertWebhook: !!process.env.SEO_ALERT_WEBHOOK_URL,
|
|
7546
|
+
alertEmail: !!process.env.SEO_ALERT_EMAIL,
|
|
7547
|
+
siteUrl: seoConfig?.siteUrl || process.env.NEXT_PUBLIC_SERVER_URL || null
|
|
7548
|
+
};
|
|
7549
|
+
const cacheStats = seoCache.stats();
|
|
7550
|
+
const auditCached = cacheStats.keys.some((k) => k === "audit" || k.startsWith("audit:"));
|
|
7551
|
+
let gscConnected = false;
|
|
7552
|
+
let gscEmail = null;
|
|
7553
|
+
let lastRankSnapshot = null;
|
|
7554
|
+
try {
|
|
7555
|
+
const authDoc = await getOrCreateGscAuthDoc(req.payload);
|
|
7556
|
+
gscConnected = !!authDoc.refreshTokenEnc;
|
|
7557
|
+
gscEmail = authDoc.connectedEmail || null;
|
|
7558
|
+
} catch {
|
|
7559
|
+
}
|
|
7560
|
+
try {
|
|
7561
|
+
const latest = await req.payload.find({
|
|
7562
|
+
collection: "seo-rank-history",
|
|
7563
|
+
sort: "-snapshotDate",
|
|
7564
|
+
limit: 1,
|
|
7565
|
+
depth: 0,
|
|
7566
|
+
overrideAccess: true
|
|
7567
|
+
});
|
|
7568
|
+
lastRankSnapshot = latest.docs[0]?.snapshotDate || null;
|
|
7569
|
+
} catch {
|
|
7570
|
+
}
|
|
7571
|
+
const warnings = [];
|
|
7572
|
+
if (!config.aiKey) warnings.push("ANTHROPIC_API_KEY not set \u2014 AI features fall back to heuristics.");
|
|
7573
|
+
if (config.gscConfigured && !gscConnected) warnings.push("GSC configured but not connected \u2014 rank tracking & CTR opportunities inactive.");
|
|
7574
|
+
if (config.gscConfigured && !config.gscEncryptionKey) warnings.push("SEO_GSC_ENCRYPTION_KEY not set \u2014 GSC token encrypted with a derived key (set an explicit key for stability).");
|
|
7575
|
+
if ((config.alertWebhook || config.alertEmail) === false) warnings.push("No alert channel configured (SEO_ALERT_WEBHOOK_URL / SEO_ALERT_EMAIL) \u2014 monitoring digest will not be delivered.");
|
|
7576
|
+
return Response.json(
|
|
7577
|
+
{
|
|
7578
|
+
ok: warnings.length === 0,
|
|
7579
|
+
config,
|
|
7580
|
+
runtime: {
|
|
7581
|
+
auditCached,
|
|
7582
|
+
cacheKeys: cacheStats.size,
|
|
7583
|
+
gscConnected,
|
|
7584
|
+
gscEmail,
|
|
7585
|
+
lastRankSnapshot
|
|
7586
|
+
},
|
|
7587
|
+
warnings
|
|
7588
|
+
},
|
|
7589
|
+
{ headers: { "Cache-Control": "no-store" } }
|
|
7590
|
+
);
|
|
7591
|
+
} catch (error) {
|
|
7592
|
+
const message = error instanceof Error ? error.message : "Internal server error";
|
|
7593
|
+
req.payload.logger.error(`[seo] health error: ${message}`);
|
|
7594
|
+
return Response.json({ error: message }, { status: 500 });
|
|
7595
|
+
}
|
|
7596
|
+
};
|
|
7597
|
+
}
|
|
7598
|
+
|
|
7526
7599
|
// src/rateLimiter.ts
|
|
7527
7600
|
function createRateLimiter(maxRequests, windowMs) {
|
|
7528
7601
|
const store = /* @__PURE__ */ new Map();
|
|
@@ -7570,7 +7643,7 @@ function getClientIp(req) {
|
|
|
7570
7643
|
|
|
7571
7644
|
// src/endpoints/seoLogs.ts
|
|
7572
7645
|
var VALID_LOG_TYPES = ["404", "redirect", "error"];
|
|
7573
|
-
function
|
|
7646
|
+
function isAdmin9(user) {
|
|
7574
7647
|
if (!user) return false;
|
|
7575
7648
|
if (user.role === "admin") return true;
|
|
7576
7649
|
if (Array.isArray(user.roles) && user.roles.includes("admin")) return true;
|
|
@@ -7667,7 +7740,7 @@ function createSeoLogsHandler(seoLogsSecret) {
|
|
|
7667
7740
|
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
|
7668
7741
|
}
|
|
7669
7742
|
if (method === "DELETE") {
|
|
7670
|
-
if (!
|
|
7743
|
+
if (!isAdmin9(req.user)) {
|
|
7671
7744
|
return Response.json({ error: "Admin access required" }, { status: 403 });
|
|
7672
7745
|
}
|
|
7673
7746
|
try {
|
|
@@ -7909,7 +7982,7 @@ function stopRankTracker() {
|
|
|
7909
7982
|
}
|
|
7910
7983
|
|
|
7911
7984
|
// src/endpoints/alerts.ts
|
|
7912
|
-
function
|
|
7985
|
+
function isAdmin10(user) {
|
|
7913
7986
|
if (!user) return false;
|
|
7914
7987
|
if (user.role === "admin") return true;
|
|
7915
7988
|
if (Array.isArray(user.roles) && user.roles.includes("admin")) return true;
|
|
@@ -8075,7 +8148,7 @@ async function deliverAlertDigest(payload, digest, cfg, siteUrl) {
|
|
|
8075
8148
|
function createAlertsDigestHandler() {
|
|
8076
8149
|
return async (req) => {
|
|
8077
8150
|
try {
|
|
8078
|
-
if (!
|
|
8151
|
+
if (!isAdmin10(req.user)) return Response.json({ error: "Forbidden" }, { status: 403 });
|
|
8079
8152
|
const cfg = getAlertConfig();
|
|
8080
8153
|
const digest = await buildAlertDigest(req.payload, cfg);
|
|
8081
8154
|
return Response.json(
|
|
@@ -8101,7 +8174,7 @@ function createAlertsDigestHandler() {
|
|
|
8101
8174
|
function createAlertsRunHandler(siteUrl) {
|
|
8102
8175
|
return async (req) => {
|
|
8103
8176
|
try {
|
|
8104
|
-
if (!
|
|
8177
|
+
if (!isAdmin10(req.user)) return Response.json({ error: "Forbidden" }, { status: 403 });
|
|
8105
8178
|
const cfg = getAlertConfig();
|
|
8106
8179
|
const digest = await buildAlertDigest(req.payload, cfg);
|
|
8107
8180
|
const delivery = await deliverAlertDigest(req.payload, digest, cfg, siteUrl);
|
|
@@ -9154,6 +9227,8 @@ var fr = {
|
|
|
9154
9227
|
bulkOptimizeMeta: "Optimiser m\xE9ta (IA)",
|
|
9155
9228
|
bulkOptimizing: "Analyse\u2026",
|
|
9156
9229
|
bulkConfirm: "Confirmer ?",
|
|
9230
|
+
optimizeSite: "Optimiser le site",
|
|
9231
|
+
optimizeSiteHint: "Cible automatiquement les pages \xE0 probl\xE8me (m\xE9ta manquante, sans mot-cl\xE9, score faible) \u2192 aper\xE7u \u2192 appliquer",
|
|
9157
9232
|
bulkPreviewTitle: "Corrections m\xE9ta propos\xE9es",
|
|
9158
9233
|
bulkApply: "Appliquer",
|
|
9159
9234
|
bulkApplying: "Application\u2026",
|
|
@@ -9756,6 +9831,8 @@ var en = {
|
|
|
9756
9831
|
bulkOptimizeMeta: "Optimize meta (AI)",
|
|
9757
9832
|
bulkOptimizing: "Analyzing\u2026",
|
|
9758
9833
|
bulkConfirm: "Confirm?",
|
|
9834
|
+
optimizeSite: "Optimize site",
|
|
9835
|
+
optimizeSiteHint: "Auto-targets pages that need work (missing meta, no keyword, low score) \u2192 preview \u2192 apply",
|
|
9759
9836
|
bulkPreviewTitle: "Proposed meta corrections",
|
|
9760
9837
|
bulkApply: "Apply",
|
|
9761
9838
|
bulkApplying: "Applying\u2026",
|
|
@@ -10694,6 +10771,11 @@ var seoAnalyzerPlugin = (pluginConfig = {}) => (incomingConfig) => {
|
|
|
10694
10771
|
handler: withRateLimit(createDuplicateContentHandler(targetCollections))
|
|
10695
10772
|
});
|
|
10696
10773
|
}
|
|
10774
|
+
pluginEndpoints.push({
|
|
10775
|
+
path: `${basePath}/health`,
|
|
10776
|
+
method: "get",
|
|
10777
|
+
handler: createSeoHealthHandler(basePath, seoConfig)
|
|
10778
|
+
});
|
|
10697
10779
|
pluginEndpoints.push(
|
|
10698
10780
|
{
|
|
10699
10781
|
path: `${basePath}/robots.txt`,
|
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1681,9 +1681,9 @@ function analyzeDoc(doc, collection, seoConfig) {
|
|
|
1681
1681
|
};
|
|
1682
1682
|
}
|
|
1683
1683
|
var CACHE_KEY = "audit";
|
|
1684
|
-
var
|
|
1685
|
-
async function buildAuditCache(payload, collections, globals, seoConfig) {
|
|
1686
|
-
const { config: mergedConfig, ignoredSlugs } = await loadMergedConfig(payload, seoConfig);
|
|
1684
|
+
var auditBuildsInFlight = /* @__PURE__ */ new Set();
|
|
1685
|
+
async function buildAuditCache(payload, collections, globals, seoConfig, reqLocale) {
|
|
1686
|
+
const { config: mergedConfig, ignoredSlugs } = await loadMergedConfig(payload, seoConfig, { reqLocale });
|
|
1687
1687
|
const BATCH_SIZE = Math.min(100, Math.max(1, parseInt(process.env.SEO_AUDIT_BATCH_SIZE || "15", 10) || 15));
|
|
1688
1688
|
const MAX_DOCS2 = Math.max(1, parseInt(process.env.SEO_AUDIT_MAX_DOCS || "1500", 10) || 1500);
|
|
1689
1689
|
const allResults = [];
|
|
@@ -1791,15 +1791,15 @@ async function buildAuditCache(payload, collections, globals, seoConfig) {
|
|
|
1791
1791
|
};
|
|
1792
1792
|
return { enrichedResults, stats, capped };
|
|
1793
1793
|
}
|
|
1794
|
-
function ensureAuditBuild(payload, collections, globals, seoConfig) {
|
|
1795
|
-
if (
|
|
1796
|
-
|
|
1797
|
-
void buildAuditCache(payload, collections, globals, seoConfig).then((result) => {
|
|
1798
|
-
seoCache.set(
|
|
1794
|
+
function ensureAuditBuild(payload, collections, globals, seoConfig, cacheKey, reqLocale) {
|
|
1795
|
+
if (auditBuildsInFlight.has(cacheKey)) return;
|
|
1796
|
+
auditBuildsInFlight.add(cacheKey);
|
|
1797
|
+
void buildAuditCache(payload, collections, globals, seoConfig, reqLocale).then((result) => {
|
|
1798
|
+
seoCache.set(cacheKey, result);
|
|
1799
1799
|
}).catch((e) => {
|
|
1800
1800
|
payload.logger.error(`[seo] audit build failed: ${e instanceof Error ? e.message : "unknown"}`);
|
|
1801
1801
|
}).finally(() => {
|
|
1802
|
-
|
|
1802
|
+
auditBuildsInFlight.delete(cacheKey);
|
|
1803
1803
|
});
|
|
1804
1804
|
}
|
|
1805
1805
|
function createAuditHandler(collections, seoConfig, globals = []) {
|
|
@@ -1812,12 +1812,14 @@ function createAuditHandler(collections, seoConfig, globals = []) {
|
|
|
1812
1812
|
const page = Math.max(1, parseInt(url.searchParams.get("page") || "1", 10));
|
|
1813
1813
|
const limit = Math.min(500, Math.max(1, parseInt(url.searchParams.get("limit") || "300", 10)));
|
|
1814
1814
|
const noCache = url.searchParams.get("nocache") === "1";
|
|
1815
|
-
|
|
1816
|
-
|
|
1815
|
+
const reqLocale = typeof req.locale === "string" && req.locale ? req.locale : void 0;
|
|
1816
|
+
const cacheKey = reqLocale ? `${CACHE_KEY}:${reqLocale}` : CACHE_KEY;
|
|
1817
|
+
if (noCache && !auditBuildsInFlight.has(cacheKey)) {
|
|
1818
|
+
seoCache.invalidateKey(cacheKey);
|
|
1817
1819
|
}
|
|
1818
|
-
const cached = seoCache.get(
|
|
1820
|
+
const cached = seoCache.get(cacheKey);
|
|
1819
1821
|
if (!cached) {
|
|
1820
|
-
ensureAuditBuild(req.payload, collections, globals, seoConfig);
|
|
1822
|
+
ensureAuditBuild(req.payload, collections, globals, seoConfig, cacheKey, reqLocale);
|
|
1821
1823
|
return Response.json(
|
|
1822
1824
|
{ building: true, results: [], stats: null },
|
|
1823
1825
|
{ status: 202, headers: { "Cache-Control": "no-store" } }
|
|
@@ -7521,6 +7523,77 @@ function createCtrOpportunitiesHandler(basePath, targetCollections, seoConfig) {
|
|
|
7521
7523
|
};
|
|
7522
7524
|
}
|
|
7523
7525
|
|
|
7526
|
+
// src/endpoints/health.ts
|
|
7527
|
+
function isAdmin8(user) {
|
|
7528
|
+
if (!user) return false;
|
|
7529
|
+
if (user.role === "admin") return true;
|
|
7530
|
+
if (Array.isArray(user.roles) && user.roles.includes("admin")) return true;
|
|
7531
|
+
return false;
|
|
7532
|
+
}
|
|
7533
|
+
function createSeoHealthHandler(basePath, seoConfig) {
|
|
7534
|
+
return async (req) => {
|
|
7535
|
+
try {
|
|
7536
|
+
if (!isAdmin8(req.user)) return Response.json({ error: "Forbidden" }, { status: 403 });
|
|
7537
|
+
const config = {
|
|
7538
|
+
aiKey: !!process.env.ANTHROPIC_API_KEY,
|
|
7539
|
+
aiModel: process.env.SEO_AI_MODEL || "claude-opus-4-8",
|
|
7540
|
+
pageSpeedKey: !!(process.env.PAGESPEED_API_KEY || process.env.GOOGLE_PAGESPEED_API_KEY),
|
|
7541
|
+
gscConfigured: !!getGscOAuthConfig(basePath, seoConfig),
|
|
7542
|
+
gscEncryptionKey: !!process.env.SEO_GSC_ENCRYPTION_KEY,
|
|
7543
|
+
alertWebhook: !!process.env.SEO_ALERT_WEBHOOK_URL,
|
|
7544
|
+
alertEmail: !!process.env.SEO_ALERT_EMAIL,
|
|
7545
|
+
siteUrl: seoConfig?.siteUrl || process.env.NEXT_PUBLIC_SERVER_URL || null
|
|
7546
|
+
};
|
|
7547
|
+
const cacheStats = seoCache.stats();
|
|
7548
|
+
const auditCached = cacheStats.keys.some((k) => k === "audit" || k.startsWith("audit:"));
|
|
7549
|
+
let gscConnected = false;
|
|
7550
|
+
let gscEmail = null;
|
|
7551
|
+
let lastRankSnapshot = null;
|
|
7552
|
+
try {
|
|
7553
|
+
const authDoc = await getOrCreateGscAuthDoc(req.payload);
|
|
7554
|
+
gscConnected = !!authDoc.refreshTokenEnc;
|
|
7555
|
+
gscEmail = authDoc.connectedEmail || null;
|
|
7556
|
+
} catch {
|
|
7557
|
+
}
|
|
7558
|
+
try {
|
|
7559
|
+
const latest = await req.payload.find({
|
|
7560
|
+
collection: "seo-rank-history",
|
|
7561
|
+
sort: "-snapshotDate",
|
|
7562
|
+
limit: 1,
|
|
7563
|
+
depth: 0,
|
|
7564
|
+
overrideAccess: true
|
|
7565
|
+
});
|
|
7566
|
+
lastRankSnapshot = latest.docs[0]?.snapshotDate || null;
|
|
7567
|
+
} catch {
|
|
7568
|
+
}
|
|
7569
|
+
const warnings = [];
|
|
7570
|
+
if (!config.aiKey) warnings.push("ANTHROPIC_API_KEY not set \u2014 AI features fall back to heuristics.");
|
|
7571
|
+
if (config.gscConfigured && !gscConnected) warnings.push("GSC configured but not connected \u2014 rank tracking & CTR opportunities inactive.");
|
|
7572
|
+
if (config.gscConfigured && !config.gscEncryptionKey) warnings.push("SEO_GSC_ENCRYPTION_KEY not set \u2014 GSC token encrypted with a derived key (set an explicit key for stability).");
|
|
7573
|
+
if ((config.alertWebhook || config.alertEmail) === false) warnings.push("No alert channel configured (SEO_ALERT_WEBHOOK_URL / SEO_ALERT_EMAIL) \u2014 monitoring digest will not be delivered.");
|
|
7574
|
+
return Response.json(
|
|
7575
|
+
{
|
|
7576
|
+
ok: warnings.length === 0,
|
|
7577
|
+
config,
|
|
7578
|
+
runtime: {
|
|
7579
|
+
auditCached,
|
|
7580
|
+
cacheKeys: cacheStats.size,
|
|
7581
|
+
gscConnected,
|
|
7582
|
+
gscEmail,
|
|
7583
|
+
lastRankSnapshot
|
|
7584
|
+
},
|
|
7585
|
+
warnings
|
|
7586
|
+
},
|
|
7587
|
+
{ headers: { "Cache-Control": "no-store" } }
|
|
7588
|
+
);
|
|
7589
|
+
} catch (error) {
|
|
7590
|
+
const message = error instanceof Error ? error.message : "Internal server error";
|
|
7591
|
+
req.payload.logger.error(`[seo] health error: ${message}`);
|
|
7592
|
+
return Response.json({ error: message }, { status: 500 });
|
|
7593
|
+
}
|
|
7594
|
+
};
|
|
7595
|
+
}
|
|
7596
|
+
|
|
7524
7597
|
// src/rateLimiter.ts
|
|
7525
7598
|
function createRateLimiter(maxRequests, windowMs) {
|
|
7526
7599
|
const store = /* @__PURE__ */ new Map();
|
|
@@ -7568,7 +7641,7 @@ function getClientIp(req) {
|
|
|
7568
7641
|
|
|
7569
7642
|
// src/endpoints/seoLogs.ts
|
|
7570
7643
|
var VALID_LOG_TYPES = ["404", "redirect", "error"];
|
|
7571
|
-
function
|
|
7644
|
+
function isAdmin9(user) {
|
|
7572
7645
|
if (!user) return false;
|
|
7573
7646
|
if (user.role === "admin") return true;
|
|
7574
7647
|
if (Array.isArray(user.roles) && user.roles.includes("admin")) return true;
|
|
@@ -7665,7 +7738,7 @@ function createSeoLogsHandler(seoLogsSecret) {
|
|
|
7665
7738
|
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
|
7666
7739
|
}
|
|
7667
7740
|
if (method === "DELETE") {
|
|
7668
|
-
if (!
|
|
7741
|
+
if (!isAdmin9(req.user)) {
|
|
7669
7742
|
return Response.json({ error: "Admin access required" }, { status: 403 });
|
|
7670
7743
|
}
|
|
7671
7744
|
try {
|
|
@@ -7907,7 +7980,7 @@ function stopRankTracker() {
|
|
|
7907
7980
|
}
|
|
7908
7981
|
|
|
7909
7982
|
// src/endpoints/alerts.ts
|
|
7910
|
-
function
|
|
7983
|
+
function isAdmin10(user) {
|
|
7911
7984
|
if (!user) return false;
|
|
7912
7985
|
if (user.role === "admin") return true;
|
|
7913
7986
|
if (Array.isArray(user.roles) && user.roles.includes("admin")) return true;
|
|
@@ -8073,7 +8146,7 @@ async function deliverAlertDigest(payload, digest, cfg, siteUrl) {
|
|
|
8073
8146
|
function createAlertsDigestHandler() {
|
|
8074
8147
|
return async (req) => {
|
|
8075
8148
|
try {
|
|
8076
|
-
if (!
|
|
8149
|
+
if (!isAdmin10(req.user)) return Response.json({ error: "Forbidden" }, { status: 403 });
|
|
8077
8150
|
const cfg = getAlertConfig();
|
|
8078
8151
|
const digest = await buildAlertDigest(req.payload, cfg);
|
|
8079
8152
|
return Response.json(
|
|
@@ -8099,7 +8172,7 @@ function createAlertsDigestHandler() {
|
|
|
8099
8172
|
function createAlertsRunHandler(siteUrl) {
|
|
8100
8173
|
return async (req) => {
|
|
8101
8174
|
try {
|
|
8102
|
-
if (!
|
|
8175
|
+
if (!isAdmin10(req.user)) return Response.json({ error: "Forbidden" }, { status: 403 });
|
|
8103
8176
|
const cfg = getAlertConfig();
|
|
8104
8177
|
const digest = await buildAlertDigest(req.payload, cfg);
|
|
8105
8178
|
const delivery = await deliverAlertDigest(req.payload, digest, cfg, siteUrl);
|
|
@@ -9152,6 +9225,8 @@ var fr = {
|
|
|
9152
9225
|
bulkOptimizeMeta: "Optimiser m\xE9ta (IA)",
|
|
9153
9226
|
bulkOptimizing: "Analyse\u2026",
|
|
9154
9227
|
bulkConfirm: "Confirmer ?",
|
|
9228
|
+
optimizeSite: "Optimiser le site",
|
|
9229
|
+
optimizeSiteHint: "Cible automatiquement les pages \xE0 probl\xE8me (m\xE9ta manquante, sans mot-cl\xE9, score faible) \u2192 aper\xE7u \u2192 appliquer",
|
|
9155
9230
|
bulkPreviewTitle: "Corrections m\xE9ta propos\xE9es",
|
|
9156
9231
|
bulkApply: "Appliquer",
|
|
9157
9232
|
bulkApplying: "Application\u2026",
|
|
@@ -9754,6 +9829,8 @@ var en = {
|
|
|
9754
9829
|
bulkOptimizeMeta: "Optimize meta (AI)",
|
|
9755
9830
|
bulkOptimizing: "Analyzing\u2026",
|
|
9756
9831
|
bulkConfirm: "Confirm?",
|
|
9832
|
+
optimizeSite: "Optimize site",
|
|
9833
|
+
optimizeSiteHint: "Auto-targets pages that need work (missing meta, no keyword, low score) \u2192 preview \u2192 apply",
|
|
9757
9834
|
bulkPreviewTitle: "Proposed meta corrections",
|
|
9758
9835
|
bulkApply: "Apply",
|
|
9759
9836
|
bulkApplying: "Applying\u2026",
|
|
@@ -10692,6 +10769,11 @@ var seoAnalyzerPlugin = (pluginConfig = {}) => (incomingConfig) => {
|
|
|
10692
10769
|
handler: withRateLimit(createDuplicateContentHandler(targetCollections))
|
|
10693
10770
|
});
|
|
10694
10771
|
}
|
|
10772
|
+
pluginEndpoints.push({
|
|
10773
|
+
path: `${basePath}/health`,
|
|
10774
|
+
method: "get",
|
|
10775
|
+
handler: createSeoHealthHandler(basePath, seoConfig)
|
|
10776
|
+
});
|
|
10695
10777
|
pluginEndpoints.push(
|
|
10696
10778
|
{
|
|
10697
10779
|
path: `${basePath}/robots.txt`,
|
package/package.json
CHANGED