@ucptools/validator 1.0.1 → 1.1.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.
Files changed (152) hide show
  1. package/dist/auth/config.d.ts +20 -0
  2. package/dist/auth/config.d.ts.map +1 -0
  3. package/dist/auth/config.js +114 -0
  4. package/dist/auth/config.js.map +1 -0
  5. package/dist/auth/index.d.ts +5 -0
  6. package/dist/auth/index.d.ts.map +1 -0
  7. package/dist/auth/index.js +17 -0
  8. package/dist/auth/index.js.map +1 -0
  9. package/dist/auth/middleware.d.ts +45 -0
  10. package/dist/auth/middleware.d.ts.map +1 -0
  11. package/dist/auth/middleware.js +170 -0
  12. package/dist/auth/middleware.js.map +1 -0
  13. package/dist/auth/service.d.ts +80 -0
  14. package/dist/auth/service.d.ts.map +1 -0
  15. package/dist/auth/service.js +298 -0
  16. package/dist/auth/service.js.map +1 -0
  17. package/dist/cli/index.js +96 -0
  18. package/dist/cli/index.js.map +1 -1
  19. package/dist/cli/mock-server.d.ts +20 -0
  20. package/dist/cli/mock-server.d.ts.map +1 -0
  21. package/dist/cli/mock-server.js +261 -0
  22. package/dist/cli/mock-server.js.map +1 -0
  23. package/dist/db/index.d.ts +8 -2
  24. package/dist/db/index.d.ts.map +1 -1
  25. package/dist/db/index.js +22 -5
  26. package/dist/db/index.js.map +1 -1
  27. package/dist/db/schema.d.ts +3570 -128
  28. package/dist/db/schema.d.ts.map +1 -1
  29. package/dist/db/schema.js +377 -17
  30. package/dist/db/schema.js.map +1 -1
  31. package/dist/db/utils.d.ts +252 -0
  32. package/dist/db/utils.d.ts.map +1 -0
  33. package/dist/db/utils.js +295 -0
  34. package/dist/db/utils.js.map +1 -0
  35. package/dist/feed-analyzer/feed-analyzer.d.ts.map +1 -1
  36. package/dist/feed-analyzer/feed-analyzer.js +218 -4
  37. package/dist/feed-analyzer/feed-analyzer.js.map +1 -1
  38. package/dist/feed-analyzer/types.d.ts +82 -1
  39. package/dist/feed-analyzer/types.d.ts.map +1 -1
  40. package/dist/feed-analyzer/types.js +13 -0
  41. package/dist/feed-analyzer/types.js.map +1 -1
  42. package/dist/lib/analytics.d.ts +337 -0
  43. package/dist/lib/analytics.d.ts.map +1 -0
  44. package/dist/lib/analytics.js +188 -0
  45. package/dist/lib/analytics.js.map +1 -0
  46. package/dist/security/security-scanner.d.ts.map +1 -1
  47. package/dist/security/security-scanner.js +130 -2
  48. package/dist/security/security-scanner.js.map +1 -1
  49. package/dist/security/types.d.ts +32 -0
  50. package/dist/security/types.d.ts.map +1 -1
  51. package/dist/security/types.js.map +1 -1
  52. package/dist/services/analytics.d.ts +114 -0
  53. package/dist/services/analytics.d.ts.map +1 -0
  54. package/dist/services/analytics.js +862 -0
  55. package/dist/services/analytics.js.map +1 -0
  56. package/dist/services/badge.d.ts +31 -0
  57. package/dist/services/badge.d.ts.map +1 -0
  58. package/dist/services/badge.js +152 -0
  59. package/dist/services/badge.js.map +1 -0
  60. package/dist/services/cron.d.ts +125 -0
  61. package/dist/services/cron.d.ts.map +1 -0
  62. package/dist/services/cron.js +613 -0
  63. package/dist/services/cron.js.map +1 -0
  64. package/dist/services/directory.d.ts +2 -0
  65. package/dist/services/directory.d.ts.map +1 -1
  66. package/dist/services/directory.js +45 -27
  67. package/dist/services/directory.js.map +1 -1
  68. package/dist/services/email.d.ts +112 -0
  69. package/dist/services/email.d.ts.map +1 -0
  70. package/dist/services/email.js +772 -0
  71. package/dist/services/email.js.map +1 -0
  72. package/dist/services/hosted-profiles.d.ts +77 -0
  73. package/dist/services/hosted-profiles.d.ts.map +1 -0
  74. package/dist/services/hosted-profiles.js +433 -0
  75. package/dist/services/hosted-profiles.js.map +1 -0
  76. package/dist/services/latency.d.ts +67 -0
  77. package/dist/services/latency.d.ts.map +1 -0
  78. package/dist/services/latency.js +274 -0
  79. package/dist/services/latency.js.map +1 -0
  80. package/dist/services/manifest-compliance.d.ts +64 -0
  81. package/dist/services/manifest-compliance.d.ts.map +1 -0
  82. package/dist/services/manifest-compliance.js +271 -0
  83. package/dist/services/manifest-compliance.js.map +1 -0
  84. package/dist/services/monitoring-diff.d.ts +31 -0
  85. package/dist/services/monitoring-diff.d.ts.map +1 -0
  86. package/dist/services/monitoring-diff.js +189 -0
  87. package/dist/services/monitoring-diff.js.map +1 -0
  88. package/dist/services/notifications.d.ts +46 -0
  89. package/dist/services/notifications.d.ts.map +1 -0
  90. package/dist/services/notifications.js +88 -0
  91. package/dist/services/notifications.js.map +1 -0
  92. package/dist/services/stripe.d.ts +93 -0
  93. package/dist/services/stripe.d.ts.map +1 -0
  94. package/dist/services/stripe.js +490 -0
  95. package/dist/services/stripe.js.map +1 -0
  96. package/dist/services/validation-history.d.ts +99 -0
  97. package/dist/services/validation-history.d.ts.map +1 -0
  98. package/dist/services/validation-history.js +344 -0
  99. package/dist/services/validation-history.js.map +1 -0
  100. package/dist/services/validation-logging.d.ts +103 -0
  101. package/dist/services/validation-logging.d.ts.map +1 -0
  102. package/dist/services/validation-logging.js +210 -0
  103. package/dist/services/validation-logging.js.map +1 -0
  104. package/dist/services/validation.d.ts +119 -0
  105. package/dist/services/validation.d.ts.map +1 -0
  106. package/dist/services/validation.js +1185 -0
  107. package/dist/services/validation.js.map +1 -0
  108. package/dist/simulator/agent-simulator.d.ts.map +1 -1
  109. package/dist/simulator/agent-simulator.js +229 -9
  110. package/dist/simulator/agent-simulator.js.map +1 -1
  111. package/dist/simulator/types.d.ts +26 -0
  112. package/dist/simulator/types.d.ts.map +1 -1
  113. package/dist/simulator/types.js.map +1 -1
  114. package/dist/types/acp-validation.d.ts +87 -0
  115. package/dist/types/acp-validation.d.ts.map +1 -0
  116. package/dist/types/acp-validation.js +40 -0
  117. package/dist/types/acp-validation.js.map +1 -0
  118. package/dist/types/analytics.d.ts +182 -0
  119. package/dist/types/analytics.d.ts.map +1 -0
  120. package/dist/types/analytics.js +7 -0
  121. package/dist/types/analytics.js.map +1 -0
  122. package/dist/types/ucp-profile.d.ts +10 -2
  123. package/dist/types/ucp-profile.d.ts.map +1 -1
  124. package/dist/types/ucp-profile.js.map +1 -1
  125. package/dist/types/validation.d.ts +8 -0
  126. package/dist/types/validation.d.ts.map +1 -1
  127. package/dist/types/validation.js +10 -0
  128. package/dist/types/validation.js.map +1 -1
  129. package/dist/validator/acp/index.d.ts +31 -0
  130. package/dist/validator/acp/index.d.ts.map +1 -0
  131. package/dist/validator/acp/index.js +574 -0
  132. package/dist/validator/acp/index.js.map +1 -0
  133. package/dist/validator/network-validator.d.ts.map +1 -1
  134. package/dist/validator/network-validator.js +4 -4
  135. package/dist/validator/network-validator.js.map +1 -1
  136. package/dist/validator/rules-validator.d.ts +8 -0
  137. package/dist/validator/rules-validator.d.ts.map +1 -1
  138. package/dist/validator/rules-validator.js +92 -43
  139. package/dist/validator/rules-validator.js.map +1 -1
  140. package/dist/validator/structural-validator.d.ts.map +1 -1
  141. package/dist/validator/structural-validator.js +187 -53
  142. package/dist/validator/structural-validator.js.map +1 -1
  143. package/dist/validator/utils.d.ts +51 -0
  144. package/dist/validator/utils.d.ts.map +1 -0
  145. package/dist/validator/utils.js +132 -0
  146. package/dist/validator/utils.js.map +1 -0
  147. package/package.json +44 -12
  148. package/.claude/settings.local.json +0 -60
  149. package/.vercel/README.txt +0 -11
  150. package/.vercel/project.json +0 -1
  151. package/publish-output.txt +0 -0
  152. package/tsconfig.json +0 -20
@@ -0,0 +1,344 @@
1
+ "use strict";
2
+ /**
3
+ * Validation History Service
4
+ *
5
+ * Manages storage and retrieval of validation history for domains.
6
+ * Supports both authenticated users (via monitoredDomains) and
7
+ * anonymous public access (with limited history).
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.storeValidationHistory = storeValidationHistory;
11
+ exports.getValidationHistory = getValidationHistory;
12
+ exports.calculateTrend = calculateTrend;
13
+ exports.getDomainHistory = getDomainHistory;
14
+ exports.getPublicDomainHistory = getPublicDomainHistory;
15
+ exports.getPreviousValidation = getPreviousValidation;
16
+ exports.updateHistorySeverity = updateHistorySeverity;
17
+ exports.validateAndStoreHistory = validateAndStoreHistory;
18
+ exports.getHistoryStats = getHistoryStats;
19
+ const index_js_1 = require("../db/index.js");
20
+ const schema_js_1 = require("../db/schema.js");
21
+ const drizzle_orm_1 = require("drizzle-orm");
22
+ const validation_js_1 = require("./validation.js");
23
+ /**
24
+ * Store a validation result in history
25
+ */
26
+ async function storeValidationHistory(domainId, result) {
27
+ try {
28
+ const db = (0, index_js_1.getDb)();
29
+ const errorsCount = result.ucp.issues.filter(i => i.severity === 'error').length +
30
+ result.schema.issues.filter(i => i.severity === 'error').length;
31
+ const warningsCount = result.ucp.issues.filter(i => i.severity === 'warn').length +
32
+ result.schema.issues.filter(i => i.severity === 'warn').length;
33
+ const [entry] = await db
34
+ .insert(schema_js_1.validationHistory)
35
+ .values({
36
+ domainId,
37
+ score: result.ai_readiness.score,
38
+ grade: result.ai_readiness.grade,
39
+ hasUcp: result.ucp.found,
40
+ issuesCount: errorsCount + warningsCount,
41
+ errorsCount,
42
+ warningsCount,
43
+ resultJson: JSON.stringify(result),
44
+ })
45
+ .returning();
46
+ // Update the monitored domain with latest results
47
+ await db
48
+ .update(schema_js_1.monitoredDomains)
49
+ .set({
50
+ lastScore: result.ai_readiness.score,
51
+ lastGrade: result.ai_readiness.grade,
52
+ lastCheckedAt: new Date(),
53
+ updatedAt: new Date(),
54
+ })
55
+ .where((0, drizzle_orm_1.eq)(schema_js_1.monitoredDomains.id, domainId));
56
+ return entry;
57
+ }
58
+ catch (error) {
59
+ console.error('Error storing validation history:', error);
60
+ return null;
61
+ }
62
+ }
63
+ /**
64
+ * Get validation history for a monitored domain (authenticated user)
65
+ */
66
+ async function getValidationHistory(domainId, limit = 100, daysBack = 90) {
67
+ try {
68
+ const db = (0, index_js_1.getDb)();
69
+ const cutoffDate = new Date();
70
+ cutoffDate.setDate(cutoffDate.getDate() - daysBack);
71
+ const history = await db
72
+ .select()
73
+ .from(schema_js_1.validationHistory)
74
+ .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_js_1.validationHistory.domainId, domainId), (0, drizzle_orm_1.gte)(schema_js_1.validationHistory.validatedAt, cutoffDate)))
75
+ .orderBy((0, drizzle_orm_1.desc)(schema_js_1.validationHistory.validatedAt))
76
+ .limit(limit);
77
+ return history;
78
+ }
79
+ catch (error) {
80
+ console.error('Error fetching validation history:', error);
81
+ return [];
82
+ }
83
+ }
84
+ /**
85
+ * Calculate trend from history entries
86
+ */
87
+ function calculateTrend(history) {
88
+ if (history.length < 2) {
89
+ return {
90
+ direction: 'insufficient_data',
91
+ scoreChange: 0,
92
+ periodDays: 0,
93
+ dataPoints: history.length,
94
+ };
95
+ }
96
+ // Sort by date descending (newest first)
97
+ const sorted = [...history].sort((a, b) => new Date(b.validatedAt).getTime() - new Date(a.validatedAt).getTime());
98
+ const latest = sorted[0];
99
+ const oldest = sorted[sorted.length - 1];
100
+ const latestScore = latest.score ?? 0;
101
+ const oldestScore = oldest.score ?? 0;
102
+ const scoreChange = latestScore - oldestScore;
103
+ const periodMs = new Date(latest.validatedAt).getTime() - new Date(oldest.validatedAt).getTime();
104
+ const periodDays = Math.ceil(periodMs / (1000 * 60 * 60 * 24));
105
+ let direction;
106
+ if (Math.abs(scoreChange) <= 5) {
107
+ direction = 'stable';
108
+ }
109
+ else if (scoreChange > 0) {
110
+ direction = 'improving';
111
+ }
112
+ else {
113
+ direction = 'declining';
114
+ }
115
+ return {
116
+ direction,
117
+ scoreChange,
118
+ periodDays,
119
+ dataPoints: history.length,
120
+ };
121
+ }
122
+ /**
123
+ * Get domain history with trend analysis (for authenticated users)
124
+ */
125
+ async function getDomainHistory(domainId, daysBack = 90) {
126
+ try {
127
+ const db = (0, index_js_1.getDb)();
128
+ // Get the domain info
129
+ const [domain] = await db
130
+ .select()
131
+ .from(schema_js_1.monitoredDomains)
132
+ .where((0, drizzle_orm_1.eq)(schema_js_1.monitoredDomains.id, domainId))
133
+ .limit(1);
134
+ if (!domain) {
135
+ return null;
136
+ }
137
+ // Get history
138
+ const history = await getValidationHistory(domainId, 100, daysBack);
139
+ const trend = calculateTrend(history);
140
+ return {
141
+ domain: domain.domain,
142
+ currentScore: domain.lastScore,
143
+ currentGrade: domain.lastGrade,
144
+ trend,
145
+ history,
146
+ lastValidatedAt: domain.lastCheckedAt,
147
+ };
148
+ }
149
+ catch (error) {
150
+ console.error('Error getting domain history:', error);
151
+ return null;
152
+ }
153
+ }
154
+ /**
155
+ * Get public history for a domain (limited to 3 entries for anonymous users)
156
+ * This looks up by domain name, not by user's monitored domain ID
157
+ */
158
+ async function getPublicDomainHistory(domainName, isAuthenticated = false) {
159
+ try {
160
+ const db = (0, index_js_1.getDb)();
161
+ const cleanDomain = domainName.replace(/^https?:\/\//, '').replace(/\/$/, '').split('/')[0];
162
+ // Find all monitored domains with this domain name
163
+ const domains = await db
164
+ .select({ id: schema_js_1.monitoredDomains.id })
165
+ .from(schema_js_1.monitoredDomains)
166
+ .where((0, drizzle_orm_1.eq)(schema_js_1.monitoredDomains.domain, cleanDomain))
167
+ .limit(1);
168
+ if (domains.length === 0) {
169
+ // No history exists for this domain
170
+ return {
171
+ domain: cleanDomain,
172
+ history: [],
173
+ trend: {
174
+ direction: 'insufficient_data',
175
+ scoreChange: 0,
176
+ periodDays: 0,
177
+ dataPoints: 0,
178
+ },
179
+ limitedAccess: !isAuthenticated,
180
+ message: 'No validation history found for this domain. Run a validation to start tracking.',
181
+ };
182
+ }
183
+ // Get history from the first matching domain
184
+ // In a more sophisticated system, we might aggregate across all users
185
+ const domainId = domains[0].id;
186
+ const limit = isAuthenticated ? 100 : 3; // Free users get 3, authenticated get 100
187
+ const history = await db
188
+ .select({
189
+ score: schema_js_1.validationHistory.score,
190
+ grade: schema_js_1.validationHistory.grade,
191
+ hasUcp: schema_js_1.validationHistory.hasUcp,
192
+ validatedAt: schema_js_1.validationHistory.validatedAt,
193
+ })
194
+ .from(schema_js_1.validationHistory)
195
+ .where((0, drizzle_orm_1.eq)(schema_js_1.validationHistory.domainId, domainId))
196
+ .orderBy((0, drizzle_orm_1.desc)(schema_js_1.validationHistory.validatedAt))
197
+ .limit(limit);
198
+ // For trend calculation, get more data even for free users
199
+ const trendHistory = await db
200
+ .select({
201
+ score: schema_js_1.validationHistory.score,
202
+ validatedAt: schema_js_1.validationHistory.validatedAt,
203
+ })
204
+ .from(schema_js_1.validationHistory)
205
+ .where((0, drizzle_orm_1.eq)(schema_js_1.validationHistory.domainId, domainId))
206
+ .orderBy((0, drizzle_orm_1.desc)(schema_js_1.validationHistory.validatedAt))
207
+ .limit(10);
208
+ const trend = calculateTrend(trendHistory.map(h => ({
209
+ id: '',
210
+ domainId: '',
211
+ score: h.score,
212
+ grade: null,
213
+ hasUcp: null,
214
+ issuesCount: null,
215
+ errorsCount: null,
216
+ warningsCount: null,
217
+ validatedAt: h.validatedAt,
218
+ })));
219
+ return {
220
+ domain: cleanDomain,
221
+ history: history.map(h => ({
222
+ score: h.score,
223
+ grade: h.grade,
224
+ hasUcp: h.hasUcp,
225
+ validatedAt: h.validatedAt,
226
+ })),
227
+ trend,
228
+ limitedAccess: !isAuthenticated,
229
+ message: !isAuthenticated && history.length >= 3
230
+ ? 'Sign up to see full validation history and trends.'
231
+ : undefined,
232
+ };
233
+ }
234
+ catch (error) {
235
+ console.error('Error getting public domain history:', error);
236
+ return {
237
+ domain: domainName,
238
+ history: [],
239
+ trend: {
240
+ direction: 'insufficient_data',
241
+ scoreChange: 0,
242
+ periodDays: 0,
243
+ dataPoints: 0,
244
+ },
245
+ limitedAccess: !isAuthenticated,
246
+ message: 'Error retrieving history.',
247
+ };
248
+ }
249
+ }
250
+ /**
251
+ * Get the previous validation entry for a domain (for diffing)
252
+ * Returns the most recent entry excluding the given ID.
253
+ */
254
+ async function getPreviousValidation(domainId, excludeId) {
255
+ try {
256
+ const db = (0, index_js_1.getDb)();
257
+ const entries = await db
258
+ .select({
259
+ id: schema_js_1.validationHistory.id,
260
+ resultJson: schema_js_1.validationHistory.resultJson,
261
+ score: schema_js_1.validationHistory.score,
262
+ grade: schema_js_1.validationHistory.grade,
263
+ })
264
+ .from(schema_js_1.validationHistory)
265
+ .where((0, drizzle_orm_1.eq)(schema_js_1.validationHistory.domainId, domainId))
266
+ .orderBy((0, drizzle_orm_1.desc)(schema_js_1.validationHistory.validatedAt))
267
+ .limit(2);
268
+ // If we have an excludeId, skip it and return the next one
269
+ if (excludeId) {
270
+ const filtered = entries.filter(e => e.id !== excludeId);
271
+ return filtered[0] ?? null;
272
+ }
273
+ // Otherwise return the second-most-recent (first is the "current")
274
+ return entries[1] ?? null;
275
+ }
276
+ catch (error) {
277
+ console.error('Error fetching previous validation:', error);
278
+ return null;
279
+ }
280
+ }
281
+ /**
282
+ * Update a validation history entry with severity and change summary
283
+ */
284
+ async function updateHistorySeverity(historyId, severity, changesSummary) {
285
+ try {
286
+ const db = (0, index_js_1.getDb)();
287
+ await db
288
+ .update(schema_js_1.validationHistory)
289
+ .set({ severity, changesSummary })
290
+ .where((0, drizzle_orm_1.eq)(schema_js_1.validationHistory.id, historyId));
291
+ }
292
+ catch (error) {
293
+ console.error('Error updating history severity:', error);
294
+ }
295
+ }
296
+ /**
297
+ * Run validation and store history for a monitored domain
298
+ */
299
+ async function validateAndStoreHistory(domainId, domainName) {
300
+ // Run the validation
301
+ const result = await (0, validation_js_1.validateDomainReadiness)(domainName);
302
+ // Store in history
303
+ const historyEntry = await storeValidationHistory(domainId, result);
304
+ return { result, historyEntry };
305
+ }
306
+ /**
307
+ * Get validation history statistics
308
+ */
309
+ async function getHistoryStats(domainId) {
310
+ try {
311
+ const db = (0, index_js_1.getDb)();
312
+ const [stats] = await db
313
+ .select({
314
+ totalValidations: (0, drizzle_orm_1.sql) `count(*)`,
315
+ averageScore: (0, drizzle_orm_1.sql) `avg(${schema_js_1.validationHistory.score})`,
316
+ bestScore: (0, drizzle_orm_1.sql) `max(${schema_js_1.validationHistory.score})`,
317
+ worstScore: (0, drizzle_orm_1.sql) `min(${schema_js_1.validationHistory.score})`,
318
+ firstValidation: (0, drizzle_orm_1.sql) `min(${schema_js_1.validationHistory.validatedAt})`,
319
+ lastValidation: (0, drizzle_orm_1.sql) `max(${schema_js_1.validationHistory.validatedAt})`,
320
+ })
321
+ .from(schema_js_1.validationHistory)
322
+ .where((0, drizzle_orm_1.eq)(schema_js_1.validationHistory.domainId, domainId));
323
+ return {
324
+ totalValidations: Number(stats?.totalValidations) || 0,
325
+ averageScore: Math.round(Number(stats?.averageScore) || 0),
326
+ bestScore: Number(stats?.bestScore) || 0,
327
+ worstScore: Number(stats?.worstScore) || 0,
328
+ firstValidation: stats?.firstValidation || null,
329
+ lastValidation: stats?.lastValidation || null,
330
+ };
331
+ }
332
+ catch (error) {
333
+ console.error('Error getting history stats:', error);
334
+ return {
335
+ totalValidations: 0,
336
+ averageScore: 0,
337
+ bestScore: 0,
338
+ worstScore: 0,
339
+ firstValidation: null,
340
+ lastValidation: null,
341
+ };
342
+ }
343
+ }
344
+ //# sourceMappingURL=validation-history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation-history.js","sourceRoot":"","sources":["../../src/services/validation-history.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Validation Logging Service
3
+ *
4
+ * Tracks all validation attempts from all users for debugging and analytics.
5
+ * Privacy-focused: IPs are hashed, no personally identifiable information stored.
6
+ */
7
+ /**
8
+ * Extract IP from request headers (handles proxies)
9
+ */
10
+ export declare function extractIp(req: {
11
+ headers: {
12
+ [key: string]: string | string[] | undefined;
13
+ };
14
+ ip?: string;
15
+ socket?: {
16
+ remoteAddress?: string;
17
+ };
18
+ }): string;
19
+ export interface LogValidationParams {
20
+ domain: string;
21
+ rawInput?: string;
22
+ success: boolean;
23
+ score?: number;
24
+ grade?: string;
25
+ hasUcp?: boolean;
26
+ errorCode?: string;
27
+ errorMessage?: string;
28
+ userAgent?: string;
29
+ ip?: string;
30
+ referer?: string;
31
+ durationMs?: number;
32
+ }
33
+ /**
34
+ * Log a validation attempt
35
+ * Fire-and-forget pattern - never blocks the main validation flow
36
+ */
37
+ export declare function logValidation(params: LogValidationParams): Promise<void>;
38
+ export interface ValidationLogFilters {
39
+ domain?: string;
40
+ success?: boolean;
41
+ errorCode?: string;
42
+ startDate?: Date;
43
+ endDate?: Date;
44
+ limit?: number;
45
+ offset?: number;
46
+ }
47
+ export interface ValidationLogStats {
48
+ totalValidations: number;
49
+ successfulValidations: number;
50
+ failedValidations: number;
51
+ successRate: number;
52
+ uniqueDomains: number;
53
+ averageScore: number | null;
54
+ topErrors: {
55
+ code: string;
56
+ count: number;
57
+ }[];
58
+ topDomains: {
59
+ domain: string;
60
+ count: number;
61
+ }[];
62
+ recentTrend: {
63
+ date: string;
64
+ count: number;
65
+ successRate: number;
66
+ }[];
67
+ }
68
+ /**
69
+ * Get validation logs with filtering
70
+ */
71
+ export declare function getValidationLogs(filters?: ValidationLogFilters): Promise<{
72
+ logs: {
73
+ id: string;
74
+ domain: string;
75
+ rawInput: string | null;
76
+ success: boolean;
77
+ score: number | null;
78
+ grade: string | null;
79
+ hasUcp: boolean | null;
80
+ errorCode: string | null;
81
+ errorMessage: string | null;
82
+ userAgent: string | null;
83
+ ipHash: string | null;
84
+ referer: string | null;
85
+ durationMs: number | null;
86
+ createdAt: Date;
87
+ }[];
88
+ total: number;
89
+ limit: number;
90
+ offset: number;
91
+ }>;
92
+ /**
93
+ * Get validation statistics
94
+ */
95
+ export declare function getValidationStats(startDate?: Date, endDate?: Date): Promise<ValidationLogStats>;
96
+ /**
97
+ * Get unique error codes for filtering UI
98
+ */
99
+ export declare function getErrorCodes(): Promise<{
100
+ code: string;
101
+ count: number;
102
+ }[]>;
103
+ //# sourceMappingURL=validation-logging.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation-logging.d.ts","sourceRoot":"","sources":["../../src/services/validation-logging.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAgBH;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE;IAAE,OAAO,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAA;KAAE,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAAG,MAAM,CActJ;AAED,MAAM,WAAW,mBAAmB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwB9E;AAED,MAAM,WAAW,oBAAoB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAChD,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACvE;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,GAAE,oBAAyB;;;;;;;;;;;;;;;;;;;;GAkDzE;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAoFtG;AAED;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAchF"}
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ /**
3
+ * Validation Logging Service
4
+ *
5
+ * Tracks all validation attempts from all users for debugging and analytics.
6
+ * Privacy-focused: IPs are hashed, no personally identifiable information stored.
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.extractIp = extractIp;
13
+ exports.logValidation = logValidation;
14
+ exports.getValidationLogs = getValidationLogs;
15
+ exports.getValidationStats = getValidationStats;
16
+ exports.getErrorCodes = getErrorCodes;
17
+ const index_js_1 = require("../db/index.js");
18
+ const schema_js_1 = require("../db/schema.js");
19
+ const drizzle_orm_1 = require("drizzle-orm");
20
+ const crypto_1 = __importDefault(require("crypto"));
21
+ /**
22
+ * Hash IP address for privacy
23
+ */
24
+ function hashIp(ip) {
25
+ // Use a salt to prevent rainbow table attacks
26
+ const salt = process.env.IP_HASH_SALT || 'ucptools-validation-logs-2026';
27
+ return crypto_1.default.createHash('sha256').update(salt + ip).digest('hex');
28
+ }
29
+ /**
30
+ * Extract IP from request headers (handles proxies)
31
+ */
32
+ function extractIp(req) {
33
+ // Check common proxy headers first
34
+ const forwarded = req.headers['x-forwarded-for'];
35
+ if (forwarded) {
36
+ const ip = Array.isArray(forwarded) ? forwarded[0] : forwarded.split(',')[0];
37
+ return ip.trim();
38
+ }
39
+ const realIp = req.headers['x-real-ip'];
40
+ if (realIp) {
41
+ return Array.isArray(realIp) ? realIp[0] : realIp;
42
+ }
43
+ return req.ip || req.socket?.remoteAddress || 'unknown';
44
+ }
45
+ /**
46
+ * Log a validation attempt
47
+ * Fire-and-forget pattern - never blocks the main validation flow
48
+ */
49
+ async function logValidation(params) {
50
+ try {
51
+ const db = (0, index_js_1.getDb)();
52
+ const logEntry = {
53
+ domain: params.domain,
54
+ rawInput: params.rawInput?.slice(0, 512), // Truncate to fit
55
+ success: params.success,
56
+ score: params.score,
57
+ grade: params.grade,
58
+ hasUcp: params.hasUcp,
59
+ errorCode: params.errorCode?.slice(0, 100),
60
+ errorMessage: params.errorMessage?.slice(0, 5000), // Reasonable limit
61
+ userAgent: params.userAgent?.slice(0, 512),
62
+ ipHash: params.ip ? hashIp(params.ip) : null,
63
+ referer: params.referer?.slice(0, 512),
64
+ durationMs: params.durationMs,
65
+ };
66
+ await db.insert(schema_js_1.validationLogs).values(logEntry);
67
+ }
68
+ catch (error) {
69
+ // Log to console but never throw - validation logging should never block
70
+ console.error('Failed to log validation:', error);
71
+ }
72
+ }
73
+ /**
74
+ * Get validation logs with filtering
75
+ */
76
+ async function getValidationLogs(filters = {}) {
77
+ const db = (0, index_js_1.getDb)();
78
+ const { domain, success, errorCode, startDate, endDate, limit = 100, offset = 0 } = filters;
79
+ // Build conditions array
80
+ const conditions = [];
81
+ if (domain) {
82
+ conditions.push((0, drizzle_orm_1.like)(schema_js_1.validationLogs.domain, `%${domain}%`));
83
+ }
84
+ if (success !== undefined) {
85
+ conditions.push((0, drizzle_orm_1.eq)(schema_js_1.validationLogs.success, success));
86
+ }
87
+ if (errorCode) {
88
+ conditions.push((0, drizzle_orm_1.eq)(schema_js_1.validationLogs.errorCode, errorCode));
89
+ }
90
+ if (startDate) {
91
+ conditions.push((0, drizzle_orm_1.gte)(schema_js_1.validationLogs.createdAt, startDate));
92
+ }
93
+ if (endDate) {
94
+ conditions.push((0, drizzle_orm_1.lte)(schema_js_1.validationLogs.createdAt, endDate));
95
+ }
96
+ // Execute query
97
+ const whereClause = conditions.length > 0 ? (0, drizzle_orm_1.and)(...conditions) : undefined;
98
+ const logs = await db
99
+ .select()
100
+ .from(schema_js_1.validationLogs)
101
+ .where(whereClause)
102
+ .orderBy((0, drizzle_orm_1.desc)(schema_js_1.validationLogs.createdAt))
103
+ .limit(Math.min(limit, 1000)) // Cap at 1000
104
+ .offset(offset);
105
+ // Get total count for pagination
106
+ const [countResult] = await db
107
+ .select({ count: (0, drizzle_orm_1.sql) `count(*)::int` })
108
+ .from(schema_js_1.validationLogs)
109
+ .where(whereClause);
110
+ return {
111
+ logs,
112
+ total: countResult?.count || 0,
113
+ limit,
114
+ offset,
115
+ };
116
+ }
117
+ /**
118
+ * Get validation statistics
119
+ */
120
+ async function getValidationStats(startDate, endDate) {
121
+ const db = (0, index_js_1.getDb)();
122
+ // Build date conditions
123
+ const dateConditions = [];
124
+ if (startDate) {
125
+ dateConditions.push((0, drizzle_orm_1.gte)(schema_js_1.validationLogs.createdAt, startDate));
126
+ }
127
+ if (endDate) {
128
+ dateConditions.push((0, drizzle_orm_1.lte)(schema_js_1.validationLogs.createdAt, endDate));
129
+ }
130
+ const whereClause = dateConditions.length > 0 ? (0, drizzle_orm_1.and)(...dateConditions) : undefined;
131
+ // Get basic stats
132
+ const [basicStats] = await db
133
+ .select({
134
+ total: (0, drizzle_orm_1.sql) `count(*)::int`,
135
+ successful: (0, drizzle_orm_1.sql) `count(*) filter (where ${schema_js_1.validationLogs.success} = true)::int`,
136
+ failed: (0, drizzle_orm_1.sql) `count(*) filter (where ${schema_js_1.validationLogs.success} = false)::int`,
137
+ uniqueDomains: (0, drizzle_orm_1.sql) `count(distinct ${schema_js_1.validationLogs.domain})::int`,
138
+ avgScore: (0, drizzle_orm_1.sql) `avg(${schema_js_1.validationLogs.score})`,
139
+ })
140
+ .from(schema_js_1.validationLogs)
141
+ .where(whereClause);
142
+ // Get top errors
143
+ const topErrors = await db
144
+ .select({
145
+ code: schema_js_1.validationLogs.errorCode,
146
+ count: (0, drizzle_orm_1.sql) `count(*)::int`,
147
+ })
148
+ .from(schema_js_1.validationLogs)
149
+ .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_js_1.validationLogs.success, false), (0, drizzle_orm_1.sql) `${schema_js_1.validationLogs.errorCode} is not null`, ...(dateConditions.length > 0 ? dateConditions : [])))
150
+ .groupBy(schema_js_1.validationLogs.errorCode)
151
+ .orderBy((0, drizzle_orm_1.desc)((0, drizzle_orm_1.sql) `count(*)`))
152
+ .limit(10);
153
+ // Get top domains
154
+ const topDomains = await db
155
+ .select({
156
+ domain: schema_js_1.validationLogs.domain,
157
+ count: (0, drizzle_orm_1.sql) `count(*)::int`,
158
+ })
159
+ .from(schema_js_1.validationLogs)
160
+ .where(whereClause)
161
+ .groupBy(schema_js_1.validationLogs.domain)
162
+ .orderBy((0, drizzle_orm_1.desc)((0, drizzle_orm_1.sql) `count(*)`))
163
+ .limit(10);
164
+ // Get recent trend (last 7 days)
165
+ const recentTrend = await db
166
+ .select({
167
+ date: (0, drizzle_orm_1.sql) `date(${schema_js_1.validationLogs.createdAt})::text`,
168
+ count: (0, drizzle_orm_1.sql) `count(*)::int`,
169
+ successRate: (0, drizzle_orm_1.sql) `(count(*) filter (where ${schema_js_1.validationLogs.success} = true)::float / nullif(count(*), 0) * 100)`,
170
+ })
171
+ .from(schema_js_1.validationLogs)
172
+ .where((0, drizzle_orm_1.gte)(schema_js_1.validationLogs.createdAt, (0, drizzle_orm_1.sql) `now() - interval '7 days'`))
173
+ .groupBy((0, drizzle_orm_1.sql) `date(${schema_js_1.validationLogs.createdAt})`)
174
+ .orderBy((0, drizzle_orm_1.sql) `date(${schema_js_1.validationLogs.createdAt})`);
175
+ const total = basicStats?.total || 0;
176
+ const successful = basicStats?.successful || 0;
177
+ const failed = basicStats?.failed || 0;
178
+ return {
179
+ totalValidations: total,
180
+ successfulValidations: successful,
181
+ failedValidations: failed,
182
+ successRate: total > 0 ? Math.round((successful / total) * 100 * 10) / 10 : 0,
183
+ uniqueDomains: basicStats?.uniqueDomains || 0,
184
+ averageScore: basicStats?.avgScore ? Math.round(basicStats.avgScore * 10) / 10 : null,
185
+ topErrors: topErrors.map(e => ({ code: e.code || 'unknown', count: e.count })),
186
+ topDomains: topDomains.map(d => ({ domain: d.domain, count: d.count })),
187
+ recentTrend: recentTrend.map(t => ({
188
+ date: t.date,
189
+ count: t.count,
190
+ successRate: Math.round((t.successRate || 0) * 10) / 10,
191
+ })),
192
+ };
193
+ }
194
+ /**
195
+ * Get unique error codes for filtering UI
196
+ */
197
+ async function getErrorCodes() {
198
+ const db = (0, index_js_1.getDb)();
199
+ const errors = await db
200
+ .select({
201
+ code: schema_js_1.validationLogs.errorCode,
202
+ count: (0, drizzle_orm_1.sql) `count(*)::int`,
203
+ })
204
+ .from(schema_js_1.validationLogs)
205
+ .where((0, drizzle_orm_1.sql) `${schema_js_1.validationLogs.errorCode} is not null`)
206
+ .groupBy(schema_js_1.validationLogs.errorCode)
207
+ .orderBy((0, drizzle_orm_1.desc)((0, drizzle_orm_1.sql) `count(*)`));
208
+ return errors.map(e => ({ code: e.code || 'unknown', count: e.count }));
209
+ }
210
+ //# sourceMappingURL=validation-logging.js.map