@consilioweb/payload-seo-analyzer 1.14.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 +293 -206
- package/dist/client.js +293 -206
- package/dist/index.cjs +81 -5
- package/dist/index.js +81 -5
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -7525,6 +7525,77 @@ function createCtrOpportunitiesHandler(basePath, targetCollections, seoConfig) {
|
|
|
7525
7525
|
};
|
|
7526
7526
|
}
|
|
7527
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
|
+
|
|
7528
7599
|
// src/rateLimiter.ts
|
|
7529
7600
|
function createRateLimiter(maxRequests, windowMs) {
|
|
7530
7601
|
const store = /* @__PURE__ */ new Map();
|
|
@@ -7572,7 +7643,7 @@ function getClientIp(req) {
|
|
|
7572
7643
|
|
|
7573
7644
|
// src/endpoints/seoLogs.ts
|
|
7574
7645
|
var VALID_LOG_TYPES = ["404", "redirect", "error"];
|
|
7575
|
-
function
|
|
7646
|
+
function isAdmin9(user) {
|
|
7576
7647
|
if (!user) return false;
|
|
7577
7648
|
if (user.role === "admin") return true;
|
|
7578
7649
|
if (Array.isArray(user.roles) && user.roles.includes("admin")) return true;
|
|
@@ -7669,7 +7740,7 @@ function createSeoLogsHandler(seoLogsSecret) {
|
|
|
7669
7740
|
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
|
7670
7741
|
}
|
|
7671
7742
|
if (method === "DELETE") {
|
|
7672
|
-
if (!
|
|
7743
|
+
if (!isAdmin9(req.user)) {
|
|
7673
7744
|
return Response.json({ error: "Admin access required" }, { status: 403 });
|
|
7674
7745
|
}
|
|
7675
7746
|
try {
|
|
@@ -7911,7 +7982,7 @@ function stopRankTracker() {
|
|
|
7911
7982
|
}
|
|
7912
7983
|
|
|
7913
7984
|
// src/endpoints/alerts.ts
|
|
7914
|
-
function
|
|
7985
|
+
function isAdmin10(user) {
|
|
7915
7986
|
if (!user) return false;
|
|
7916
7987
|
if (user.role === "admin") return true;
|
|
7917
7988
|
if (Array.isArray(user.roles) && user.roles.includes("admin")) return true;
|
|
@@ -8077,7 +8148,7 @@ async function deliverAlertDigest(payload, digest, cfg, siteUrl) {
|
|
|
8077
8148
|
function createAlertsDigestHandler() {
|
|
8078
8149
|
return async (req) => {
|
|
8079
8150
|
try {
|
|
8080
|
-
if (!
|
|
8151
|
+
if (!isAdmin10(req.user)) return Response.json({ error: "Forbidden" }, { status: 403 });
|
|
8081
8152
|
const cfg = getAlertConfig();
|
|
8082
8153
|
const digest = await buildAlertDigest(req.payload, cfg);
|
|
8083
8154
|
return Response.json(
|
|
@@ -8103,7 +8174,7 @@ function createAlertsDigestHandler() {
|
|
|
8103
8174
|
function createAlertsRunHandler(siteUrl) {
|
|
8104
8175
|
return async (req) => {
|
|
8105
8176
|
try {
|
|
8106
|
-
if (!
|
|
8177
|
+
if (!isAdmin10(req.user)) return Response.json({ error: "Forbidden" }, { status: 403 });
|
|
8107
8178
|
const cfg = getAlertConfig();
|
|
8108
8179
|
const digest = await buildAlertDigest(req.payload, cfg);
|
|
8109
8180
|
const delivery = await deliverAlertDigest(req.payload, digest, cfg, siteUrl);
|
|
@@ -10700,6 +10771,11 @@ var seoAnalyzerPlugin = (pluginConfig = {}) => (incomingConfig) => {
|
|
|
10700
10771
|
handler: withRateLimit(createDuplicateContentHandler(targetCollections))
|
|
10701
10772
|
});
|
|
10702
10773
|
}
|
|
10774
|
+
pluginEndpoints.push({
|
|
10775
|
+
path: `${basePath}/health`,
|
|
10776
|
+
method: "get",
|
|
10777
|
+
handler: createSeoHealthHandler(basePath, seoConfig)
|
|
10778
|
+
});
|
|
10703
10779
|
pluginEndpoints.push(
|
|
10704
10780
|
{
|
|
10705
10781
|
path: `${basePath}/robots.txt`,
|
package/dist/index.js
CHANGED
|
@@ -7523,6 +7523,77 @@ function createCtrOpportunitiesHandler(basePath, targetCollections, seoConfig) {
|
|
|
7523
7523
|
};
|
|
7524
7524
|
}
|
|
7525
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
|
+
|
|
7526
7597
|
// src/rateLimiter.ts
|
|
7527
7598
|
function createRateLimiter(maxRequests, windowMs) {
|
|
7528
7599
|
const store = /* @__PURE__ */ new Map();
|
|
@@ -7570,7 +7641,7 @@ function getClientIp(req) {
|
|
|
7570
7641
|
|
|
7571
7642
|
// src/endpoints/seoLogs.ts
|
|
7572
7643
|
var VALID_LOG_TYPES = ["404", "redirect", "error"];
|
|
7573
|
-
function
|
|
7644
|
+
function isAdmin9(user) {
|
|
7574
7645
|
if (!user) return false;
|
|
7575
7646
|
if (user.role === "admin") return true;
|
|
7576
7647
|
if (Array.isArray(user.roles) && user.roles.includes("admin")) return true;
|
|
@@ -7667,7 +7738,7 @@ function createSeoLogsHandler(seoLogsSecret) {
|
|
|
7667
7738
|
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
|
7668
7739
|
}
|
|
7669
7740
|
if (method === "DELETE") {
|
|
7670
|
-
if (!
|
|
7741
|
+
if (!isAdmin9(req.user)) {
|
|
7671
7742
|
return Response.json({ error: "Admin access required" }, { status: 403 });
|
|
7672
7743
|
}
|
|
7673
7744
|
try {
|
|
@@ -7909,7 +7980,7 @@ function stopRankTracker() {
|
|
|
7909
7980
|
}
|
|
7910
7981
|
|
|
7911
7982
|
// src/endpoints/alerts.ts
|
|
7912
|
-
function
|
|
7983
|
+
function isAdmin10(user) {
|
|
7913
7984
|
if (!user) return false;
|
|
7914
7985
|
if (user.role === "admin") return true;
|
|
7915
7986
|
if (Array.isArray(user.roles) && user.roles.includes("admin")) return true;
|
|
@@ -8075,7 +8146,7 @@ async function deliverAlertDigest(payload, digest, cfg, siteUrl) {
|
|
|
8075
8146
|
function createAlertsDigestHandler() {
|
|
8076
8147
|
return async (req) => {
|
|
8077
8148
|
try {
|
|
8078
|
-
if (!
|
|
8149
|
+
if (!isAdmin10(req.user)) return Response.json({ error: "Forbidden" }, { status: 403 });
|
|
8079
8150
|
const cfg = getAlertConfig();
|
|
8080
8151
|
const digest = await buildAlertDigest(req.payload, cfg);
|
|
8081
8152
|
return Response.json(
|
|
@@ -8101,7 +8172,7 @@ function createAlertsDigestHandler() {
|
|
|
8101
8172
|
function createAlertsRunHandler(siteUrl) {
|
|
8102
8173
|
return async (req) => {
|
|
8103
8174
|
try {
|
|
8104
|
-
if (!
|
|
8175
|
+
if (!isAdmin10(req.user)) return Response.json({ error: "Forbidden" }, { status: 403 });
|
|
8105
8176
|
const cfg = getAlertConfig();
|
|
8106
8177
|
const digest = await buildAlertDigest(req.payload, cfg);
|
|
8107
8178
|
const delivery = await deliverAlertDigest(req.payload, digest, cfg, siteUrl);
|
|
@@ -10698,6 +10769,11 @@ var seoAnalyzerPlugin = (pluginConfig = {}) => (incomingConfig) => {
|
|
|
10698
10769
|
handler: withRateLimit(createDuplicateContentHandler(targetCollections))
|
|
10699
10770
|
});
|
|
10700
10771
|
}
|
|
10772
|
+
pluginEndpoints.push({
|
|
10773
|
+
path: `${basePath}/health`,
|
|
10774
|
+
method: "get",
|
|
10775
|
+
handler: createSeoHealthHandler(basePath, seoConfig)
|
|
10776
|
+
});
|
|
10701
10777
|
pluginEndpoints.push(
|
|
10702
10778
|
{
|
|
10703
10779
|
path: `${basePath}/robots.txt`,
|
package/package.json
CHANGED