Files
didnt-read/src/services/searchIndexer.js
2026-01-27 13:24:03 -05:00

189 lines
4.8 KiB
JavaScript

/**
* Meilisearch indexer for search functionality
*/
import meilisearch from '../config/meilisearch.js';
import { Service } from '../models/Service.js';
import { Analysis } from '../models/Analysis.js';
const INDEX_NAME = 'services';
export class SearchIndexer {
static index = null;
/**
* Initialize the search index
*/
static async init() {
try {
console.log('Initializing Meilisearch...');
// Check if index exists
const indexes = await meilisearch.getIndexes();
const indexExists = indexes.results.some(idx => idx.uid === INDEX_NAME);
if (!indexExists) {
console.log('Creating search index...');
await meilisearch.createIndex(INDEX_NAME, { primaryKey: 'id' });
}
this.index = meilisearch.index(INDEX_NAME);
// Configure searchable attributes
await this.index.updateSettings({
searchableAttributes: [
'name',
'findings.positive.title',
'findings.positive.description',
'findings.negative.title',
'findings.negative.description',
'data_types_collected',
'third_parties.name'
],
filterableAttributes: ['grade', 'overall_score'],
sortableAttributes: ['name', 'created_at', 'overall_score'],
rankingRules: [
'words',
'typo',
'proximity',
'attribute',
'sort',
'exactness'
]
});
console.log('Meilisearch initialized successfully');
} catch (error) {
console.error('Meilisearch initialization error:', error);
throw error;
}
}
/**
* Index a single service
* @param {Object} service - Service with analysis
*/
static async indexService(service) {
try {
if (!this.index) {
await this.init();
}
const document = {
id: service.id,
name: service.name,
url: service.url,
logo_url: service.logo_url,
grade: service.grade,
overall_score: service.overall_score,
findings: service.findings || { positive: [], negative: [], neutral: [] },
data_types_collected: service.data_types_collected || [],
third_parties: service.third_parties || [],
last_analyzed: service.last_analyzed,
created_at: service.created_at
};
await this.index.addDocuments([document]);
console.log(`Indexed service: ${service.name}`);
} catch (error) {
console.error(`Failed to index service ${service.name}:`, error);
throw error;
}
}
/**
* Index all services
*/
static async indexAll() {
try {
console.log('Indexing all services...');
const services = await Service.findAllWithLatestAnalysis();
const documents = services.map(service => ({
id: service.id,
name: service.name,
url: service.url,
logo_url: service.logo_url,
grade: service.grade,
overall_score: service.overall_score,
findings: service.findings || { positive: [], negative: [], neutral: [] },
data_types_collected: service.data_types_collected || [],
third_parties: service.third_parties || [],
last_analyzed: service.last_analyzed,
created_at: service.created_at
}));
if (documents.length > 0) {
await this.index.addDocuments(documents);
console.log(`Indexed ${documents.length} services`);
}
return { indexed: documents.length };
} catch (error) {
console.error('Bulk indexing error:', error);
throw error;
}
}
/**
* Search services
* @param {string} query - Search query
* @param {Object} options - Search options
*/
static async search(query, options = {}) {
try {
if (!this.index) {
await this.init();
}
const searchOptions = {
limit: options.limit || 25,
offset: options.offset || 0,
...options
};
const results = await this.index.search(query, searchOptions);
return results;
} catch (error) {
console.error('Search error:', error);
throw error;
}
}
/**
* Delete a service from the index
* @param {number} serviceId - Service ID
*/
static async deleteService(serviceId) {
try {
if (!this.index) {
await this.init();
}
await this.index.deleteDocument(serviceId);
console.log(`Deleted service ${serviceId} from index`);
} catch (error) {
console.error(`Failed to delete service ${serviceId}:`, error);
throw error;
}
}
/**
* Get index stats
*/
static async getStats() {
try {
if (!this.index) {
await this.init();
}
const stats = await this.index.getStats();
return stats;
} catch (error) {
console.error('Failed to get stats:', error);
throw error;
}
}
}