@ucptools/validator 1.0.1 → 1.2.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/auth/config.d.ts +20 -0
- package/dist/auth/config.d.ts.map +1 -0
- package/dist/auth/config.js +114 -0
- package/dist/auth/config.js.map +1 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +17 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/middleware.d.ts +45 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +170 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/service.d.ts +80 -0
- package/dist/auth/service.d.ts.map +1 -0
- package/dist/auth/service.js +298 -0
- package/dist/auth/service.js.map +1 -0
- package/dist/cli/index.js +96 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mock-server.d.ts +20 -0
- package/dist/cli/mock-server.d.ts.map +1 -0
- package/dist/cli/mock-server.js +261 -0
- package/dist/cli/mock-server.js.map +1 -0
- package/dist/db/index.d.ts +8 -2
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +22 -5
- package/dist/db/index.js.map +1 -1
- package/dist/db/schema.d.ts +3570 -128
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +377 -17
- package/dist/db/schema.js.map +1 -1
- package/dist/db/utils.d.ts +252 -0
- package/dist/db/utils.d.ts.map +1 -0
- package/dist/db/utils.js +295 -0
- package/dist/db/utils.js.map +1 -0
- package/dist/feed-analyzer/feed-analyzer.d.ts.map +1 -1
- package/dist/feed-analyzer/feed-analyzer.js +218 -4
- package/dist/feed-analyzer/feed-analyzer.js.map +1 -1
- package/dist/feed-analyzer/types.d.ts +82 -1
- package/dist/feed-analyzer/types.d.ts.map +1 -1
- package/dist/feed-analyzer/types.js +13 -0
- package/dist/feed-analyzer/types.js.map +1 -1
- package/dist/generator/profile-builder.d.ts.map +1 -1
- package/dist/generator/profile-builder.js +158 -115
- package/dist/generator/profile-builder.js.map +1 -1
- package/dist/lib/analytics.d.ts +349 -0
- package/dist/lib/analytics.d.ts.map +1 -0
- package/dist/lib/analytics.js +198 -0
- package/dist/lib/analytics.js.map +1 -0
- package/dist/security/security-scanner.d.ts.map +1 -1
- package/dist/security/security-scanner.js +130 -2
- package/dist/security/security-scanner.js.map +1 -1
- package/dist/security/types.d.ts +32 -0
- package/dist/security/types.d.ts.map +1 -1
- package/dist/security/types.js.map +1 -1
- package/dist/services/analytics.d.ts +114 -0
- package/dist/services/analytics.d.ts.map +1 -0
- package/dist/services/analytics.js +862 -0
- package/dist/services/analytics.js.map +1 -0
- package/dist/services/badge.d.ts +31 -0
- package/dist/services/badge.d.ts.map +1 -0
- package/dist/services/badge.js +152 -0
- package/dist/services/badge.js.map +1 -0
- package/dist/services/cron.d.ts +127 -0
- package/dist/services/cron.d.ts.map +1 -0
- package/dist/services/cron.js +693 -0
- package/dist/services/cron.js.map +1 -0
- package/dist/services/directory.d.ts +2 -0
- package/dist/services/directory.d.ts.map +1 -1
- package/dist/services/directory.js +45 -27
- package/dist/services/directory.js.map +1 -1
- package/dist/services/email.d.ts +127 -0
- package/dist/services/email.d.ts.map +1 -0
- package/dist/services/email.js +876 -0
- package/dist/services/email.js.map +1 -0
- package/dist/services/hosted-profiles.d.ts +77 -0
- package/dist/services/hosted-profiles.d.ts.map +1 -0
- package/dist/services/hosted-profiles.js +433 -0
- package/dist/services/hosted-profiles.js.map +1 -0
- package/dist/services/latency.d.ts +67 -0
- package/dist/services/latency.d.ts.map +1 -0
- package/dist/services/latency.js +274 -0
- package/dist/services/latency.js.map +1 -0
- package/dist/services/manifest-compliance.d.ts +64 -0
- package/dist/services/manifest-compliance.d.ts.map +1 -0
- package/dist/services/manifest-compliance.js +271 -0
- package/dist/services/manifest-compliance.js.map +1 -0
- package/dist/services/monitoring-diff.d.ts +31 -0
- package/dist/services/monitoring-diff.d.ts.map +1 -0
- package/dist/services/monitoring-diff.js +189 -0
- package/dist/services/monitoring-diff.js.map +1 -0
- package/dist/services/notifications.d.ts +46 -0
- package/dist/services/notifications.d.ts.map +1 -0
- package/dist/services/notifications.js +88 -0
- package/dist/services/notifications.js.map +1 -0
- package/dist/services/posthog.d.ts +43 -0
- package/dist/services/posthog.d.ts.map +1 -0
- package/dist/services/posthog.js +110 -0
- package/dist/services/posthog.js.map +1 -0
- package/dist/services/stripe.d.ts +93 -0
- package/dist/services/stripe.d.ts.map +1 -0
- package/dist/services/stripe.js +490 -0
- package/dist/services/stripe.js.map +1 -0
- package/dist/services/validation-history.d.ts +99 -0
- package/dist/services/validation-history.d.ts.map +1 -0
- package/dist/services/validation-history.js +344 -0
- package/dist/services/validation-history.js.map +1 -0
- package/dist/services/validation-logging.d.ts +103 -0
- package/dist/services/validation-logging.d.ts.map +1 -0
- package/dist/services/validation-logging.js +210 -0
- package/dist/services/validation-logging.js.map +1 -0
- package/dist/services/validation.d.ts +119 -0
- package/dist/services/validation.d.ts.map +1 -0
- package/dist/services/validation.js +1185 -0
- package/dist/services/validation.js.map +1 -0
- package/dist/simulator/agent-simulator.d.ts.map +1 -1
- package/dist/simulator/agent-simulator.js +229 -9
- package/dist/simulator/agent-simulator.js.map +1 -1
- package/dist/simulator/types.d.ts +26 -0
- package/dist/simulator/types.d.ts.map +1 -1
- package/dist/simulator/types.js.map +1 -1
- package/dist/types/acp-validation.d.ts +87 -0
- package/dist/types/acp-validation.d.ts.map +1 -0
- package/dist/types/acp-validation.js +40 -0
- package/dist/types/acp-validation.js.map +1 -0
- package/dist/types/analytics.d.ts +182 -0
- package/dist/types/analytics.d.ts.map +1 -0
- package/dist/types/analytics.js +7 -0
- package/dist/types/analytics.js.map +1 -0
- package/dist/types/generator.d.ts +4 -0
- package/dist/types/generator.d.ts.map +1 -1
- package/dist/types/ucp-profile.d.ts +32 -2
- package/dist/types/ucp-profile.d.ts.map +1 -1
- package/dist/types/ucp-profile.js +31 -1
- package/dist/types/ucp-profile.js.map +1 -1
- package/dist/types/validation.d.ts +14 -0
- package/dist/types/validation.d.ts.map +1 -1
- package/dist/types/validation.js +19 -0
- package/dist/types/validation.js.map +1 -1
- package/dist/validator/acp/index.d.ts +31 -0
- package/dist/validator/acp/index.d.ts.map +1 -0
- package/dist/validator/acp/index.js +574 -0
- package/dist/validator/acp/index.js.map +1 -0
- package/dist/validator/network-validator.d.ts.map +1 -1
- package/dist/validator/network-validator.js +23 -13
- package/dist/validator/network-validator.js.map +1 -1
- package/dist/validator/rules-validator.d.ts +8 -0
- package/dist/validator/rules-validator.d.ts.map +1 -1
- package/dist/validator/rules-validator.js +159 -43
- package/dist/validator/rules-validator.js.map +1 -1
- package/dist/validator/structural-validator.d.ts.map +1 -1
- package/dist/validator/structural-validator.js +283 -53
- package/dist/validator/structural-validator.js.map +1 -1
- package/dist/validator/utils.d.ts +62 -0
- package/dist/validator/utils.d.ts.map +1 -0
- package/dist/validator/utils.js +151 -0
- package/dist/validator/utils.js.map +1 -0
- package/package.json +45 -12
- package/.claude/settings.local.json +0 -60
- package/.vercel/README.txt +0 -11
- package/.vercel/project.json +0 -1
- package/publish-output.txt +0 -0
- package/tsconfig.json +0 -20
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Cron Service
|
|
4
|
+
*
|
|
5
|
+
* Handles scheduled tasks like weekly domain validation.
|
|
6
|
+
* Designed to be triggered by Vercel Cron, external scheduler, or manual API call.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.getDomainsNeedingValidation = getDomainsNeedingValidation;
|
|
10
|
+
exports.runWeeklyValidation = runWeeklyValidation;
|
|
11
|
+
exports.getCronStats = getCronStats;
|
|
12
|
+
exports.runTrialEmailSequence = runTrialEmailSequence;
|
|
13
|
+
exports.runHostedProfileMaintenance = runHostedProfileMaintenance;
|
|
14
|
+
exports.runExpireTrials = runExpireTrials;
|
|
15
|
+
const index_js_1 = require("../db/index.js");
|
|
16
|
+
const schema_js_1 = require("../db/schema.js");
|
|
17
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
18
|
+
const validation_history_js_1 = require("./validation-history.js");
|
|
19
|
+
const email_js_1 = require("./email.js");
|
|
20
|
+
const monitoring_diff_js_1 = require("./monitoring-diff.js");
|
|
21
|
+
const notifications_js_1 = require("./notifications.js");
|
|
22
|
+
const hosted_profiles_js_1 = require("./hosted-profiles.js");
|
|
23
|
+
const posthog_js_1 = require("./posthog.js");
|
|
24
|
+
/**
|
|
25
|
+
* Get all domains that need validation based on their monitoring frequency.
|
|
26
|
+
* - Must be active
|
|
27
|
+
* - User must have active or trialing subscription
|
|
28
|
+
* - Filters by frequency: daily → 24h threshold, weekly → 7d threshold
|
|
29
|
+
* - Never-checked domains are always included
|
|
30
|
+
*/
|
|
31
|
+
async function getDomainsNeedingValidation() {
|
|
32
|
+
const db = (0, index_js_1.getDb)();
|
|
33
|
+
// Get all active domains with active/trialing subscriptions
|
|
34
|
+
const domains = await db
|
|
35
|
+
.select({
|
|
36
|
+
id: schema_js_1.monitoredDomains.id,
|
|
37
|
+
domain: schema_js_1.monitoredDomains.domain,
|
|
38
|
+
userId: schema_js_1.monitoredDomains.userId,
|
|
39
|
+
userEmail: schema_js_1.users.email,
|
|
40
|
+
userName: schema_js_1.users.name,
|
|
41
|
+
lastCheckedAt: schema_js_1.monitoredDomains.lastCheckedAt,
|
|
42
|
+
lastScore: schema_js_1.monitoredDomains.lastScore,
|
|
43
|
+
lastGrade: schema_js_1.monitoredDomains.lastGrade,
|
|
44
|
+
alertOnScoreChange: schema_js_1.monitoredDomains.alertOnScoreChange,
|
|
45
|
+
alertOnGradeChange: schema_js_1.monitoredDomains.alertOnGradeChange,
|
|
46
|
+
alertThreshold: schema_js_1.monitoredDomains.alertThreshold,
|
|
47
|
+
monitoringFrequency: schema_js_1.monitoredDomains.monitoringFrequency,
|
|
48
|
+
webhookUrl: schema_js_1.monitoredDomains.webhookUrl,
|
|
49
|
+
webhookEnabled: schema_js_1.monitoredDomains.webhookEnabled,
|
|
50
|
+
})
|
|
51
|
+
.from(schema_js_1.monitoredDomains)
|
|
52
|
+
.innerJoin(schema_js_1.users, (0, drizzle_orm_1.eq)(schema_js_1.monitoredDomains.userId, schema_js_1.users.id))
|
|
53
|
+
.innerJoin(schema_js_1.subscriptions, (0, drizzle_orm_1.eq)(schema_js_1.users.id, schema_js_1.subscriptions.userId))
|
|
54
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_js_1.monitoredDomains.isActive, true), (0, drizzle_orm_1.or)((0, drizzle_orm_1.eq)(schema_js_1.subscriptions.status, 'active'), (0, drizzle_orm_1.eq)(schema_js_1.subscriptions.status, 'trialing'), (0, drizzle_orm_1.eq)(schema_js_1.subscriptions.status, 'expired'))));
|
|
55
|
+
// Filter in JS by frequency threshold
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
|
58
|
+
const SEVEN_DAYS_MS = 7 * ONE_DAY_MS;
|
|
59
|
+
return domains.filter(d => {
|
|
60
|
+
// Never checked → always include
|
|
61
|
+
if (!d.lastCheckedAt)
|
|
62
|
+
return true;
|
|
63
|
+
const elapsed = now - new Date(d.lastCheckedAt).getTime();
|
|
64
|
+
const frequency = d.monitoringFrequency || 'weekly';
|
|
65
|
+
if (frequency === 'daily') {
|
|
66
|
+
return elapsed >= ONE_DAY_MS;
|
|
67
|
+
}
|
|
68
|
+
// weekly (default)
|
|
69
|
+
return elapsed >= SEVEN_DAYS_MS;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Run domain validation for all eligible domains (frequency-aware).
|
|
74
|
+
* Includes diff analysis, severity classification, and webhook dispatch.
|
|
75
|
+
*
|
|
76
|
+
* @param maxDomains - Maximum number of domains to process (for rate limiting)
|
|
77
|
+
* @param delayBetweenMs - Delay between validations in milliseconds
|
|
78
|
+
*/
|
|
79
|
+
async function runWeeklyValidation(maxDomains = 100, delayBetweenMs = 1000) {
|
|
80
|
+
const startTime = Date.now();
|
|
81
|
+
const result = {
|
|
82
|
+
success: true,
|
|
83
|
+
domainsProcessed: 0,
|
|
84
|
+
domainsValidated: 0,
|
|
85
|
+
domainsFailed: 0,
|
|
86
|
+
alertsSent: 0,
|
|
87
|
+
alertsFailed: 0,
|
|
88
|
+
webhooksSent: 0,
|
|
89
|
+
errors: [],
|
|
90
|
+
durationMs: 0,
|
|
91
|
+
};
|
|
92
|
+
const dashboardBaseUrl = process.env.DASHBOARD_URL || 'https://ucptools.dev/dashboard';
|
|
93
|
+
try {
|
|
94
|
+
const domains = await getDomainsNeedingValidation();
|
|
95
|
+
const toProcess = domains.slice(0, maxDomains);
|
|
96
|
+
console.log(`[Cron] Found ${domains.length} domains needing validation, processing ${toProcess.length}`);
|
|
97
|
+
for (const domainInfo of toProcess) {
|
|
98
|
+
result.domainsProcessed++;
|
|
99
|
+
try {
|
|
100
|
+
console.log(`[Cron] Validating domain: ${domainInfo.domain}`);
|
|
101
|
+
// Run validation and store history
|
|
102
|
+
const { result: validationResult, historyEntry } = await (0, validation_history_js_1.validateAndStoreHistory)(domainInfo.id, domainInfo.domain);
|
|
103
|
+
result.domainsValidated++;
|
|
104
|
+
const newScore = validationResult.ai_readiness.score;
|
|
105
|
+
const newGrade = validationResult.ai_readiness.grade;
|
|
106
|
+
console.log(`[Cron] Successfully validated: ${domainInfo.domain} (score: ${newScore})`);
|
|
107
|
+
// ── Diff analysis ──────────────────────────────────────────
|
|
108
|
+
let diff = null;
|
|
109
|
+
const previousEntry = await (0, validation_history_js_1.getPreviousValidation)(domainInfo.id, historyEntry?.id);
|
|
110
|
+
if (previousEntry?.resultJson) {
|
|
111
|
+
try {
|
|
112
|
+
const previousResult = JSON.parse(previousEntry.resultJson);
|
|
113
|
+
diff = (0, monitoring_diff_js_1.diffValidationResults)(previousResult, validationResult, domainInfo.alertThreshold ?? 10);
|
|
114
|
+
// Store severity + changes summary on the history entry
|
|
115
|
+
if (historyEntry) {
|
|
116
|
+
await (0, validation_history_js_1.updateHistorySeverity)(historyEntry.id, diff.severity, JSON.stringify({ text: diff.summary, changes: diff.changes }));
|
|
117
|
+
}
|
|
118
|
+
// Update lastSeverity on the monitored domain
|
|
119
|
+
const db = (0, index_js_1.getDb)();
|
|
120
|
+
await db
|
|
121
|
+
.update(schema_js_1.monitoredDomains)
|
|
122
|
+
.set({ lastSeverity: diff.severity, updatedAt: new Date() })
|
|
123
|
+
.where((0, drizzle_orm_1.eq)(schema_js_1.monitoredDomains.id, domainInfo.id));
|
|
124
|
+
console.log(`[Cron] Diff for ${domainInfo.domain}: severity=${diff.severity}, changes=${diff.changes.length}`);
|
|
125
|
+
}
|
|
126
|
+
catch (diffError) {
|
|
127
|
+
console.error(`[Cron] Diff error for ${domainInfo.domain}:`, diffError);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// ── Severity-based alerting ────────────────────────────────
|
|
131
|
+
const severity = diff?.severity;
|
|
132
|
+
const dashboardUrl = `${dashboardBaseUrl}/domains/${domainInfo.id}`;
|
|
133
|
+
const shouldEmail = severity === 'broken'
|
|
134
|
+
|| (severity === 'degraded' && (domainInfo.alertOnScoreChange || domainInfo.alertOnGradeChange))
|
|
135
|
+
|| (!diff && domainInfo.alertOnScoreChange && domainInfo.lastScore !== null && Math.abs(newScore - domainInfo.lastScore) >= (domainInfo.alertThreshold ?? 10));
|
|
136
|
+
if (shouldEmail) {
|
|
137
|
+
try {
|
|
138
|
+
let emailResult;
|
|
139
|
+
// Use severity-aware template when we have diff data
|
|
140
|
+
if (diff && (severity === 'broken' || severity === 'degraded' || severity === 'changed')) {
|
|
141
|
+
emailResult = await (0, email_js_1.sendSeverityAlert)({
|
|
142
|
+
to: {
|
|
143
|
+
email: domainInfo.userEmail,
|
|
144
|
+
name: domainInfo.userName || undefined,
|
|
145
|
+
},
|
|
146
|
+
domain: domainInfo.domain,
|
|
147
|
+
severity,
|
|
148
|
+
oldScore: domainInfo.lastScore,
|
|
149
|
+
newScore,
|
|
150
|
+
oldGrade: domainInfo.lastGrade,
|
|
151
|
+
newGrade,
|
|
152
|
+
summary: diff.summary,
|
|
153
|
+
changes: diff.changes.map(c => ({
|
|
154
|
+
field: c.field,
|
|
155
|
+
type: c.type,
|
|
156
|
+
oldValue: c.oldValue,
|
|
157
|
+
newValue: c.newValue,
|
|
158
|
+
})),
|
|
159
|
+
dashboardUrl,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
// Fallback to basic score change alert
|
|
164
|
+
emailResult = await (0, email_js_1.sendScoreChangeAlert)({
|
|
165
|
+
to: {
|
|
166
|
+
email: domainInfo.userEmail,
|
|
167
|
+
name: domainInfo.userName || undefined,
|
|
168
|
+
},
|
|
169
|
+
domain: domainInfo.domain,
|
|
170
|
+
oldScore: domainInfo.lastScore ?? 0,
|
|
171
|
+
newScore,
|
|
172
|
+
oldGrade: domainInfo.lastGrade || 'F',
|
|
173
|
+
newGrade,
|
|
174
|
+
dashboardUrl,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
if (emailResult.success) {
|
|
178
|
+
result.alertsSent++;
|
|
179
|
+
console.log(`[Cron] Alert sent to ${domainInfo.userEmail} (severity: ${severity || 'score-change'})`);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
result.alertsFailed++;
|
|
183
|
+
console.error(`[Cron] Failed to send alert: ${emailResult.error}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch (emailError) {
|
|
187
|
+
result.alertsFailed++;
|
|
188
|
+
console.error(`[Cron] Email error:`, emailError);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// ── Webhook dispatch ───────────────────────────────────────
|
|
192
|
+
if (diff && diff.severity !== 'stable' && domainInfo.webhookEnabled && domainInfo.webhookUrl) {
|
|
193
|
+
const eventMap = {
|
|
194
|
+
broken: 'profile_broken',
|
|
195
|
+
degraded: 'profile_degraded',
|
|
196
|
+
changed: 'profile_changed',
|
|
197
|
+
};
|
|
198
|
+
const payload = {
|
|
199
|
+
event: eventMap[diff.severity] || 'profile_changed',
|
|
200
|
+
domain: domainInfo.domain,
|
|
201
|
+
severity: diff.severity,
|
|
202
|
+
oldScore: domainInfo.lastScore,
|
|
203
|
+
newScore,
|
|
204
|
+
oldGrade: domainInfo.lastGrade,
|
|
205
|
+
newGrade,
|
|
206
|
+
changes: diff.changes.map(c => ({
|
|
207
|
+
field: c.field,
|
|
208
|
+
type: c.type,
|
|
209
|
+
oldValue: c.oldValue,
|
|
210
|
+
newValue: c.newValue,
|
|
211
|
+
})),
|
|
212
|
+
summary: diff.summary,
|
|
213
|
+
timestamp: new Date().toISOString(),
|
|
214
|
+
dashboardUrl,
|
|
215
|
+
};
|
|
216
|
+
const sent = await (0, notifications_js_1.sendDomainWebhook)(domainInfo.webhookUrl, payload);
|
|
217
|
+
if (sent)
|
|
218
|
+
result.webhooksSent++;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
result.domainsFailed++;
|
|
223
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
224
|
+
result.errors.push({ domain: domainInfo.domain, error: errorMessage });
|
|
225
|
+
console.error(`[Cron] Failed to validate ${domainInfo.domain}:`, errorMessage);
|
|
226
|
+
}
|
|
227
|
+
// Add delay between validations to avoid overwhelming the system
|
|
228
|
+
if (delayBetweenMs > 0 && result.domainsProcessed < toProcess.length) {
|
|
229
|
+
await sleep(delayBetweenMs);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
result.success = result.domainsFailed === 0;
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
result.success = false;
|
|
236
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
237
|
+
result.errors.push({ domain: 'system', error: errorMessage });
|
|
238
|
+
console.error('[Cron] Domain validation failed:', errorMessage);
|
|
239
|
+
}
|
|
240
|
+
result.durationMs = Date.now() - startTime;
|
|
241
|
+
console.log(`[Cron] Completed: ${result.domainsValidated}/${result.domainsProcessed} validated, ${result.alertsSent} alerts, ${result.webhooksSent} webhooks in ${result.durationMs}ms`);
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Get cron status/stats
|
|
246
|
+
*/
|
|
247
|
+
async function getCronStats() {
|
|
248
|
+
const domains = await getDomainsNeedingValidation();
|
|
249
|
+
// Next run is typically handled by external scheduler
|
|
250
|
+
// For Vercel Cron, this would be configured in vercel.json
|
|
251
|
+
const nextRunAt = new Date();
|
|
252
|
+
nextRunAt.setDate(nextRunAt.getDate() + 7);
|
|
253
|
+
nextRunAt.setHours(0, 0, 0, 0);
|
|
254
|
+
return {
|
|
255
|
+
pendingDomains: domains.length,
|
|
256
|
+
lastRunAt: null, // Would need separate tracking for this
|
|
257
|
+
nextRunAt,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function sleep(ms) {
|
|
261
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Send trial sequence emails to users
|
|
265
|
+
*
|
|
266
|
+
* - Day 3: Value reminder + Feature highlight
|
|
267
|
+
* - Day 6: Trial ending + Conversion CTA
|
|
268
|
+
*
|
|
269
|
+
* This should be run daily via cron job.
|
|
270
|
+
*/
|
|
271
|
+
async function runTrialEmailSequence() {
|
|
272
|
+
const startTime = Date.now();
|
|
273
|
+
const result = {
|
|
274
|
+
success: true,
|
|
275
|
+
day1Sent: 0,
|
|
276
|
+
day1Failed: 0,
|
|
277
|
+
day2Sent: 0,
|
|
278
|
+
day2Failed: 0,
|
|
279
|
+
day3Sent: 0,
|
|
280
|
+
day3Failed: 0,
|
|
281
|
+
day6Sent: 0,
|
|
282
|
+
day6Failed: 0,
|
|
283
|
+
errors: [],
|
|
284
|
+
durationMs: 0,
|
|
285
|
+
};
|
|
286
|
+
const dashboardBaseUrl = process.env.DASHBOARD_URL || 'https://ucptools.dev/dashboard';
|
|
287
|
+
try {
|
|
288
|
+
const db = (0, index_js_1.getDb)();
|
|
289
|
+
// Get trialing users with their trial start dates
|
|
290
|
+
const trialingUsers = await db
|
|
291
|
+
.select({
|
|
292
|
+
userId: schema_js_1.users.id,
|
|
293
|
+
email: schema_js_1.users.email,
|
|
294
|
+
name: schema_js_1.users.name,
|
|
295
|
+
trialStartedAt: schema_js_1.subscriptions.createdAt,
|
|
296
|
+
currentPeriodEnd: schema_js_1.subscriptions.currentPeriodEnd,
|
|
297
|
+
})
|
|
298
|
+
.from(schema_js_1.subscriptions)
|
|
299
|
+
.innerJoin(schema_js_1.users, (0, drizzle_orm_1.eq)(schema_js_1.subscriptions.userId, schema_js_1.users.id))
|
|
300
|
+
.where((0, drizzle_orm_1.eq)(schema_js_1.subscriptions.status, 'trialing'));
|
|
301
|
+
console.log(`[Cron] Found ${trialingUsers.length} users in trial`);
|
|
302
|
+
const now = new Date();
|
|
303
|
+
for (const user of trialingUsers) {
|
|
304
|
+
if (!user.trialStartedAt)
|
|
305
|
+
continue;
|
|
306
|
+
const trialStart = new Date(user.trialStartedAt);
|
|
307
|
+
const daysSinceTrialStart = Math.floor((now.getTime() - trialStart.getTime()) / (1000 * 60 * 60 * 24));
|
|
308
|
+
// Get user's domain count for personalization
|
|
309
|
+
const domainCount = await db
|
|
310
|
+
.select({ count: schema_js_1.monitoredDomains.id })
|
|
311
|
+
.from(schema_js_1.monitoredDomains)
|
|
312
|
+
.where((0, drizzle_orm_1.eq)(schema_js_1.monitoredDomains.userId, user.userId))
|
|
313
|
+
.then((rows) => rows.length);
|
|
314
|
+
// Day 1 email - Quick Start Guide
|
|
315
|
+
if (daysSinceTrialStart === 1) {
|
|
316
|
+
try {
|
|
317
|
+
console.log(`[Cron] Sending Day 1 email to ${user.email}`);
|
|
318
|
+
const emailResult = await (0, email_js_1.sendTrialDay1Email)({
|
|
319
|
+
to: {
|
|
320
|
+
email: user.email,
|
|
321
|
+
name: user.name || undefined,
|
|
322
|
+
},
|
|
323
|
+
dashboardUrl: dashboardBaseUrl,
|
|
324
|
+
});
|
|
325
|
+
(0, posthog_js_1.trackEmailSent)({ userId: user.userId, email: user.email, emailType: 'trial_day1', success: emailResult.success });
|
|
326
|
+
if (emailResult.success) {
|
|
327
|
+
result.day1Sent++;
|
|
328
|
+
console.log(`[Cron] Day 1 email sent to ${user.email}`);
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
result.day1Failed++;
|
|
332
|
+
result.errors.push({
|
|
333
|
+
userId: user.userId,
|
|
334
|
+
email: user.email,
|
|
335
|
+
error: emailResult.error || 'Unknown error',
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
result.day1Failed++;
|
|
341
|
+
result.errors.push({
|
|
342
|
+
userId: user.userId,
|
|
343
|
+
email: user.email,
|
|
344
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// Day 2 email - Personalized Domain Report
|
|
349
|
+
if (daysSinceTrialStart === 2) {
|
|
350
|
+
try {
|
|
351
|
+
console.log(`[Cron] Sending Day 2 email to ${user.email}`);
|
|
352
|
+
// Get the user's first domain for personalization
|
|
353
|
+
const userDomains = await db
|
|
354
|
+
.select({
|
|
355
|
+
id: schema_js_1.monitoredDomains.id,
|
|
356
|
+
domain: schema_js_1.monitoredDomains.domain,
|
|
357
|
+
lastScore: schema_js_1.monitoredDomains.lastScore,
|
|
358
|
+
lastGrade: schema_js_1.monitoredDomains.lastGrade,
|
|
359
|
+
})
|
|
360
|
+
.from(schema_js_1.monitoredDomains)
|
|
361
|
+
.where((0, drizzle_orm_1.eq)(schema_js_1.monitoredDomains.userId, user.userId))
|
|
362
|
+
.limit(1);
|
|
363
|
+
const firstDomain = userDomains[0];
|
|
364
|
+
let topIssues = [];
|
|
365
|
+
// Get latest validation result for top issues
|
|
366
|
+
if (firstDomain) {
|
|
367
|
+
const latestHistory = await db
|
|
368
|
+
.select({ resultJson: schema_js_1.validationHistory.resultJson })
|
|
369
|
+
.from(schema_js_1.validationHistory)
|
|
370
|
+
.where((0, drizzle_orm_1.eq)(schema_js_1.validationHistory.domainId, firstDomain.id))
|
|
371
|
+
.orderBy((0, drizzle_orm_1.desc)(schema_js_1.validationHistory.validatedAt))
|
|
372
|
+
.limit(1);
|
|
373
|
+
if (latestHistory[0]?.resultJson) {
|
|
374
|
+
try {
|
|
375
|
+
const lastResult = JSON.parse(latestHistory[0].resultJson);
|
|
376
|
+
const issues = lastResult?.issues || lastResult?.findings || [];
|
|
377
|
+
topIssues = issues
|
|
378
|
+
.filter((i) => i.severity === 'error' || i.severity === 'warn')
|
|
379
|
+
.slice(0, 5)
|
|
380
|
+
.map((i) => ({ message: i.message, severity: i.severity }));
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
// Ignore JSON parse errors
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const emailResult = await (0, email_js_1.sendTrialDay2Email)({
|
|
388
|
+
to: {
|
|
389
|
+
email: user.email,
|
|
390
|
+
name: user.name || undefined,
|
|
391
|
+
},
|
|
392
|
+
dashboardUrl: dashboardBaseUrl,
|
|
393
|
+
domain: firstDomain?.domain,
|
|
394
|
+
score: firstDomain?.lastScore ?? undefined,
|
|
395
|
+
grade: firstDomain?.lastGrade ?? undefined,
|
|
396
|
+
topIssues,
|
|
397
|
+
});
|
|
398
|
+
(0, posthog_js_1.trackEmailSent)({ userId: user.userId, email: user.email, emailType: 'trial_day2', success: emailResult.success });
|
|
399
|
+
if (emailResult.success) {
|
|
400
|
+
result.day2Sent++;
|
|
401
|
+
console.log(`[Cron] Day 2 email sent to ${user.email}`);
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
result.day2Failed++;
|
|
405
|
+
result.errors.push({
|
|
406
|
+
userId: user.userId,
|
|
407
|
+
email: user.email,
|
|
408
|
+
error: emailResult.error || 'Unknown error',
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
result.day2Failed++;
|
|
414
|
+
result.errors.push({
|
|
415
|
+
userId: user.userId,
|
|
416
|
+
email: user.email,
|
|
417
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
// Day 3 email (send on day 3)
|
|
422
|
+
if (daysSinceTrialStart === 3) {
|
|
423
|
+
try {
|
|
424
|
+
console.log(`[Cron] Sending Day 3 email to ${user.email}`);
|
|
425
|
+
const emailResult = await (0, email_js_1.sendTrialDay3Email)({
|
|
426
|
+
to: {
|
|
427
|
+
email: user.email,
|
|
428
|
+
name: user.name || undefined,
|
|
429
|
+
},
|
|
430
|
+
dashboardUrl: dashboardBaseUrl,
|
|
431
|
+
domainsAdded: domainCount,
|
|
432
|
+
});
|
|
433
|
+
(0, posthog_js_1.trackEmailSent)({ userId: user.userId, email: user.email, emailType: 'trial_day3', success: emailResult.success });
|
|
434
|
+
if (emailResult.success) {
|
|
435
|
+
result.day3Sent++;
|
|
436
|
+
console.log(`[Cron] Day 3 email sent to ${user.email}`);
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
result.day3Failed++;
|
|
440
|
+
result.errors.push({
|
|
441
|
+
userId: user.userId,
|
|
442
|
+
email: user.email,
|
|
443
|
+
error: emailResult.error || 'Unknown error',
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
result.day3Failed++;
|
|
449
|
+
result.errors.push({
|
|
450
|
+
userId: user.userId,
|
|
451
|
+
email: user.email,
|
|
452
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
// Day 6 email (send on day 6)
|
|
457
|
+
if (daysSinceTrialStart === 6) {
|
|
458
|
+
try {
|
|
459
|
+
console.log(`[Cron] Sending Day 6 email to ${user.email}`);
|
|
460
|
+
// Get validation count for this user
|
|
461
|
+
const validationCount = await db
|
|
462
|
+
.select({ count: schema_js_1.monitoredDomains.id })
|
|
463
|
+
.from(schema_js_1.monitoredDomains)
|
|
464
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_js_1.monitoredDomains.userId, user.userId),
|
|
465
|
+
// Has been validated at least once
|
|
466
|
+
(0, drizzle_orm_1.lt)(schema_js_1.monitoredDomains.lastCheckedAt, now)))
|
|
467
|
+
.then((rows) => rows.length);
|
|
468
|
+
const emailResult = await (0, email_js_1.sendTrialDay6Email)({
|
|
469
|
+
to: {
|
|
470
|
+
email: user.email,
|
|
471
|
+
name: user.name || undefined,
|
|
472
|
+
},
|
|
473
|
+
dashboardUrl: dashboardBaseUrl,
|
|
474
|
+
domainsAdded: domainCount,
|
|
475
|
+
validationsRun: validationCount,
|
|
476
|
+
});
|
|
477
|
+
(0, posthog_js_1.trackEmailSent)({ userId: user.userId, email: user.email, emailType: 'trial_day6', success: emailResult.success });
|
|
478
|
+
if (emailResult.success) {
|
|
479
|
+
result.day6Sent++;
|
|
480
|
+
console.log(`[Cron] Day 6 email sent to ${user.email}`);
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
result.day6Failed++;
|
|
484
|
+
result.errors.push({
|
|
485
|
+
userId: user.userId,
|
|
486
|
+
email: user.email,
|
|
487
|
+
error: emailResult.error || 'Unknown error',
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
catch (error) {
|
|
492
|
+
result.day6Failed++;
|
|
493
|
+
result.errors.push({
|
|
494
|
+
userId: user.userId,
|
|
495
|
+
email: user.email,
|
|
496
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
// Small delay between emails to avoid rate limits
|
|
501
|
+
await sleep(100);
|
|
502
|
+
}
|
|
503
|
+
result.success =
|
|
504
|
+
result.day1Failed === 0 && result.day2Failed === 0 && result.day3Failed === 0 && result.day6Failed === 0;
|
|
505
|
+
}
|
|
506
|
+
catch (error) {
|
|
507
|
+
result.success = false;
|
|
508
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
509
|
+
result.errors.push({ userId: 'system', email: '', error: errorMessage });
|
|
510
|
+
console.error('[Cron] Trial email sequence failed:', errorMessage);
|
|
511
|
+
}
|
|
512
|
+
result.durationMs = Date.now() - startTime;
|
|
513
|
+
console.log(`[Cron] Trial emails completed: Day 1: ${result.day1Sent}, Day 2: ${result.day2Sent}, Day 3: ${result.day3Sent}, Day 6: ${result.day6Sent} sent in ${result.durationMs}ms`);
|
|
514
|
+
return result;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Run validation on active hosted profiles that haven't been validated in 7+ days.
|
|
518
|
+
* Also checks pending DNS verifications.
|
|
519
|
+
*/
|
|
520
|
+
async function runHostedProfileMaintenance(maxProfiles = 100, delayBetweenMs = 500) {
|
|
521
|
+
const startTime = Date.now();
|
|
522
|
+
const result = {
|
|
523
|
+
success: true,
|
|
524
|
+
profilesValidated: 0,
|
|
525
|
+
profilesFailed: 0,
|
|
526
|
+
verificationsChecked: 0,
|
|
527
|
+
verificationsConfirmed: 0,
|
|
528
|
+
errors: [],
|
|
529
|
+
durationMs: 0,
|
|
530
|
+
};
|
|
531
|
+
try {
|
|
532
|
+
// 1. Re-validate active profiles needing validation
|
|
533
|
+
const profilesToValidate = await (0, hosted_profiles_js_1.getProfilesNeedingValidation)(maxProfiles);
|
|
534
|
+
console.log(`[Cron] Found ${profilesToValidate.length} hosted profiles needing validation`);
|
|
535
|
+
for (const profile of profilesToValidate) {
|
|
536
|
+
try {
|
|
537
|
+
await (0, hosted_profiles_js_1.validateHostedProfile)(profile.id);
|
|
538
|
+
result.profilesValidated++;
|
|
539
|
+
}
|
|
540
|
+
catch (error) {
|
|
541
|
+
result.profilesFailed++;
|
|
542
|
+
result.errors.push({
|
|
543
|
+
profileId: profile.id,
|
|
544
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
if (delayBetweenMs > 0) {
|
|
548
|
+
await sleep(delayBetweenMs);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
// 2. Check pending verifications
|
|
552
|
+
const pendingVerifications = await (0, hosted_profiles_js_1.getProfilesPendingVerification)(50);
|
|
553
|
+
console.log(`[Cron] Found ${pendingVerifications.length} profiles pending DNS verification`);
|
|
554
|
+
for (const profile of pendingVerifications) {
|
|
555
|
+
result.verificationsChecked++;
|
|
556
|
+
try {
|
|
557
|
+
const check = await (0, hosted_profiles_js_1.checkDnsVerification)(profile.id);
|
|
558
|
+
if (check.verified) {
|
|
559
|
+
result.verificationsConfirmed++;
|
|
560
|
+
console.log(`[Cron] DNS verified for ${profile.domain}`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
catch (error) {
|
|
564
|
+
result.errors.push({
|
|
565
|
+
profileId: profile.id,
|
|
566
|
+
error: error instanceof Error ? error.message : 'DNS check failed',
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
if (delayBetweenMs > 0) {
|
|
570
|
+
await sleep(delayBetweenMs);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
result.success = result.profilesFailed === 0;
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
result.success = false;
|
|
577
|
+
result.errors.push({
|
|
578
|
+
profileId: 'system',
|
|
579
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
580
|
+
});
|
|
581
|
+
console.error('[Cron] Hosted profile maintenance failed:', error);
|
|
582
|
+
}
|
|
583
|
+
result.durationMs = Date.now() - startTime;
|
|
584
|
+
console.log(`[Cron] Hosted profile maintenance: ${result.profilesValidated} validated, ${result.verificationsConfirmed}/${result.verificationsChecked} DNS verified in ${result.durationMs}ms`);
|
|
585
|
+
return result;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Expire trials that have passed their currentPeriodEnd date.
|
|
589
|
+
*
|
|
590
|
+
* Downgrades users from trialing/starter → canceled/free.
|
|
591
|
+
* Sends a "trial expired" email with upgrade CTA.
|
|
592
|
+
*
|
|
593
|
+
* Should be run daily via cron job (same schedule as trial-emails).
|
|
594
|
+
*/
|
|
595
|
+
async function runExpireTrials() {
|
|
596
|
+
const startTime = Date.now();
|
|
597
|
+
const result = {
|
|
598
|
+
success: true,
|
|
599
|
+
expired: 0,
|
|
600
|
+
emailsSent: 0,
|
|
601
|
+
emailsFailed: 0,
|
|
602
|
+
errors: [],
|
|
603
|
+
durationMs: 0,
|
|
604
|
+
};
|
|
605
|
+
const dashboardBaseUrl = process.env.DASHBOARD_URL || 'https://ucptools.dev/dashboard';
|
|
606
|
+
try {
|
|
607
|
+
const db = (0, index_js_1.getDb)();
|
|
608
|
+
const now = new Date();
|
|
609
|
+
// Find all trialing subscriptions where currentPeriodEnd has passed
|
|
610
|
+
const expiredTrials = await db
|
|
611
|
+
.select({
|
|
612
|
+
userId: schema_js_1.subscriptions.userId,
|
|
613
|
+
currentPeriodEnd: schema_js_1.subscriptions.currentPeriodEnd,
|
|
614
|
+
userEmail: schema_js_1.users.email,
|
|
615
|
+
userName: schema_js_1.users.name,
|
|
616
|
+
})
|
|
617
|
+
.from(schema_js_1.subscriptions)
|
|
618
|
+
.innerJoin(schema_js_1.users, (0, drizzle_orm_1.eq)(schema_js_1.subscriptions.userId, schema_js_1.users.id))
|
|
619
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_js_1.subscriptions.status, 'trialing'), (0, drizzle_orm_1.lt)(schema_js_1.subscriptions.currentPeriodEnd, now)));
|
|
620
|
+
console.log(`[Cron] Found ${expiredTrials.length} expired trials to downgrade`);
|
|
621
|
+
for (const trial of expiredTrials) {
|
|
622
|
+
try {
|
|
623
|
+
// Expire trial — keep tier so monitoring continues, set status to expired
|
|
624
|
+
await db
|
|
625
|
+
.update(schema_js_1.subscriptions)
|
|
626
|
+
.set({
|
|
627
|
+
status: 'expired',
|
|
628
|
+
updatedAt: new Date(),
|
|
629
|
+
})
|
|
630
|
+
.where((0, drizzle_orm_1.eq)(schema_js_1.subscriptions.userId, trial.userId));
|
|
631
|
+
result.expired++;
|
|
632
|
+
console.log(`[Cron] Expired trial for user ${trial.userId} (${trial.userEmail})`);
|
|
633
|
+
// Get domain count for email personalization
|
|
634
|
+
const domainCount = await db
|
|
635
|
+
.select({ id: schema_js_1.monitoredDomains.id })
|
|
636
|
+
.from(schema_js_1.monitoredDomains)
|
|
637
|
+
.where((0, drizzle_orm_1.eq)(schema_js_1.monitoredDomains.userId, trial.userId))
|
|
638
|
+
.then((rows) => rows.length);
|
|
639
|
+
// Send trial expired email
|
|
640
|
+
try {
|
|
641
|
+
const emailResult = await (0, email_js_1.sendTrialExpiredEmail)({
|
|
642
|
+
to: {
|
|
643
|
+
email: trial.userEmail,
|
|
644
|
+
name: trial.userName || undefined,
|
|
645
|
+
},
|
|
646
|
+
dashboardUrl: dashboardBaseUrl,
|
|
647
|
+
domainsAdded: domainCount,
|
|
648
|
+
});
|
|
649
|
+
(0, posthog_js_1.trackEmailSent)({ userId: trial.userId, email: trial.userEmail, emailType: 'trial_expired', success: emailResult.success });
|
|
650
|
+
if (emailResult.success) {
|
|
651
|
+
result.emailsSent++;
|
|
652
|
+
console.log(`[Cron] Trial expired email sent to ${trial.userEmail}`);
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
result.emailsFailed++;
|
|
656
|
+
result.errors.push({
|
|
657
|
+
userId: trial.userId,
|
|
658
|
+
error: `Email failed: ${emailResult.error}`,
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
catch (emailError) {
|
|
663
|
+
result.emailsFailed++;
|
|
664
|
+
result.errors.push({
|
|
665
|
+
userId: trial.userId,
|
|
666
|
+
error: `Email error: ${emailError instanceof Error ? emailError.message : 'Unknown'}`,
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
catch (error) {
|
|
671
|
+
result.errors.push({
|
|
672
|
+
userId: trial.userId,
|
|
673
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
674
|
+
});
|
|
675
|
+
console.error(`[Cron] Failed to expire trial for ${trial.userId}:`, error);
|
|
676
|
+
}
|
|
677
|
+
await sleep(100);
|
|
678
|
+
}
|
|
679
|
+
result.success = result.errors.length === 0;
|
|
680
|
+
}
|
|
681
|
+
catch (error) {
|
|
682
|
+
result.success = false;
|
|
683
|
+
result.errors.push({
|
|
684
|
+
userId: 'system',
|
|
685
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
686
|
+
});
|
|
687
|
+
console.error('[Cron] Trial expiration failed:', error);
|
|
688
|
+
}
|
|
689
|
+
result.durationMs = Date.now() - startTime;
|
|
690
|
+
console.log(`[Cron] Trial expiration completed: ${result.expired} expired, ${result.emailsSent} emails sent in ${result.durationMs}ms`);
|
|
691
|
+
return result;
|
|
692
|
+
}
|
|
693
|
+
//# sourceMappingURL=cron.js.map
|