2026-04-16-2
This commit is contained in:
@@ -40,4 +40,13 @@ export class MapCandidate extends BasePlace {
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
metadata: any; // For photos, extra contact info, etc.
|
||||
|
||||
@Column({ nullable: true })
|
||||
verified_name: string;
|
||||
|
||||
@Column({ type: 'int', default: 0 })
|
||||
confidence_score: number;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
verification_metadata: any;
|
||||
}
|
||||
|
||||
@@ -77,8 +77,13 @@ export class MapRefinementService {
|
||||
});
|
||||
|
||||
const saved = await this.candidateRepository.save(candidate);
|
||||
this.logger.log(`✅ Success: Candidate ${saved.id} enriched with Gov:${saved.governorate_id}, Neigh:${saved.neighborhood_id}`);
|
||||
this.logger.log(`✅ Success: Candidate ${saved.id} enriched with Gov:${saved.governorate_id}, Neigh:${spatialData.neigh_id}`);
|
||||
|
||||
// Trigger automated verification in background (don't block the response)
|
||||
this.runExternalVerification(saved.id).catch(err =>
|
||||
this.logger.error(`❌ Verification failed for ${saved.id}: ${err.message}`)
|
||||
);
|
||||
|
||||
// Return with enriched names for immediate feedback
|
||||
return {
|
||||
...saved,
|
||||
@@ -89,6 +94,67 @@ export class MapRefinementService {
|
||||
} as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automated verification using Search Engine Metadata Scraping
|
||||
*/
|
||||
async runExternalVerification(candidateId: number): Promise<void> {
|
||||
const candidate = await this.candidateRepository.findOne({ where: { id: candidateId } });
|
||||
if (!candidate) return;
|
||||
|
||||
this.logger.log(`🔍 Starting automated verification for candidate ${candidate.id}...`);
|
||||
|
||||
try {
|
||||
const axios = require('axios');
|
||||
// Using DuckDuckGo HTML search for coordinates - very stable for scraping names
|
||||
const searchUrl = `https://duckduckgo.com/html/?q=${candidate.latitude},${candidate.longitude}`;
|
||||
|
||||
const response = await axios.get(searchUrl, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
||||
},
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
const html = response.data;
|
||||
const titleMatch = html.match(/<title>([^<]+)<\/title>/);
|
||||
|
||||
if (titleMatch) {
|
||||
let officialName = titleMatch[1].replace(' at DuckDuckGo', '').replace(' - DuckDuckGo', '').trim();
|
||||
|
||||
// If the name is just coordinates, it means no POI was specifically identified
|
||||
const isCoordinates = /^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$/.test(officialName);
|
||||
|
||||
if (!isCoordinates && officialName.length > 5) {
|
||||
candidate.verified_name = officialName;
|
||||
|
||||
// Basic confidence: if the user's name is partially in the official name, high trust
|
||||
const cleanUser = candidate.name.toLowerCase().trim();
|
||||
const cleanOfficial = officialName.toLowerCase();
|
||||
|
||||
if (cleanOfficial.includes(cleanUser) || cleanUser.includes(cleanOfficial)) {
|
||||
candidate.confidence_score = 90;
|
||||
} else {
|
||||
candidate.confidence_score = 60; // Found a name, but didn't match perfectly
|
||||
}
|
||||
|
||||
this.logger.log(`🎯 Verified: Found "${officialName}" near suggestion. Trust: ${candidate.confidence_score}%`);
|
||||
} else {
|
||||
candidate.confidence_score = 10; // Only coordinates found
|
||||
this.logger.log(`⚠️ No specific POI name found for coordinates ${candidate.latitude},${candidate.longitude}`);
|
||||
}
|
||||
}
|
||||
|
||||
candidate.verification_metadata = {
|
||||
last_check: new Date().toISOString(),
|
||||
source: 'DDG_SCRAPE'
|
||||
};
|
||||
|
||||
await this.candidateRepository.save(candidate);
|
||||
} catch (error) {
|
||||
this.logger.error(`Automated verification error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async getCandidates(status?: CandidateStatus): Promise<any[]> {
|
||||
const query = `
|
||||
SELECT
|
||||
|
||||
@@ -10,8 +10,12 @@ async function bootstrap() {
|
||||
logger: ['error', 'warn', 'log', 'debug', 'verbose'],
|
||||
});
|
||||
|
||||
// 1. Modern Security Headers (Prevention of XSS, Clickjacking, etc.)
|
||||
app.enableCors();
|
||||
// 1. Modern Security Headers & Permissive CORS for Production Dashboard
|
||||
app.enableCors({
|
||||
origin: true, // Reflect request origin
|
||||
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
|
||||
credentials: true,
|
||||
});
|
||||
|
||||
// 3. Global API prefix
|
||||
app.setGlobalPrefix('api');
|
||||
|
||||
Reference in New Issue
Block a user