navidocs/server/config/meilisearch.js
ggq-admin 155a8c0305 feat: NaviDocs MVP - Complete codebase extraction from lilian1
## Backend (server/)
- Express 5 API with security middleware (helmet, rate limiting)
- SQLite database with WAL mode (schema from docs/architecture/)
- Meilisearch integration with tenant tokens
- BullMQ + Redis background job queue
- OCR pipeline with Tesseract.js
- File safety validation (extension, MIME, size)
- 4 API route modules: upload, jobs, search, documents

## Frontend (client/)
- Vue 3 with Composition API (<script setup>)
- Vite 5 build system with HMR
- Tailwind CSS (Meilisearch-inspired design)
- UploadModal with drag-and-drop
- FigureZoom component (ported from lilian1)
- Meilisearch search integration with tenant tokens
- Job polling composable
- Clean SVG icons (no emojis)

## Code Extraction
-  manuals.js → UploadModal.vue, useJobPolling.js
-  figure-zoom.js → FigureZoom.vue
-  service-worker.js → client/public/service-worker.js (TODO)
-  glossary.json → Merged into Meilisearch synonyms
-  Discarded: quiz.js, persona.js, gamification.js (Frank-AI junk)

## Documentation
- Complete extraction plan in docs/analysis/
- README with quick start guide
- Architecture summary in docs/architecture/

## Build Status
- Server dependencies:  Installed (234 packages)
- Client dependencies:  Installed (160 packages)
- Client build:  Successful (2.63s)

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 01:55:44 +02:00

86 lines
2.5 KiB
JavaScript

/**
* Meilisearch client configuration
*/
import { MeiliSearch } from 'meilisearch';
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __dirname = dirname(fileURLToPath(import.meta.url));
const MEILISEARCH_HOST = process.env.MEILISEARCH_HOST || 'http://127.0.0.1:7700';
const MEILISEARCH_MASTER_KEY = process.env.MEILISEARCH_MASTER_KEY || 'masterKey';
const INDEX_NAME = process.env.MEILISEARCH_INDEX_NAME || 'navidocs-pages';
let client = null;
let index = null;
export function getMeilisearchClient() {
if (!client) {
client = new MeiliSearch({
host: MEILISEARCH_HOST,
apiKey: MEILISEARCH_MASTER_KEY
});
}
return client;
}
export async function getMeilisearchIndex() {
if (!index) {
const client = getMeilisearchClient();
try {
index = await client.getIndex(INDEX_NAME);
} catch (error) {
// Index doesn't exist, create it
console.log('Creating Meilisearch index:', INDEX_NAME);
await client.createIndex(INDEX_NAME, { primaryKey: 'id' });
index = await client.getIndex(INDEX_NAME);
// Configure index settings
await configureIndex(index);
}
}
return index;
}
async function configureIndex(index) {
// Load config from docs
const configPath = join(__dirname, '../../docs/architecture/meilisearch-config.json');
const config = JSON.parse(readFileSync(configPath, 'utf8'));
await index.updateSettings({
searchableAttributes: config.settings.searchableAttributes,
filterableAttributes: config.settings.filterableAttributes,
sortableAttributes: config.settings.sortableAttributes,
displayedAttributes: config.settings.displayedAttributes,
synonyms: config.settings.synonyms,
stopWords: config.settings.stopWords,
rankingRules: config.settings.rankingRules,
typoTolerance: config.settings.typoTolerance,
faceting: config.settings.faceting,
pagination: config.settings.pagination,
separatorTokens: config.settings.separatorTokens,
nonSeparatorTokens: config.settings.nonSeparatorTokens
});
console.log('Meilisearch index configured');
}
export function generateTenantToken(userId, organizationIds, expiresIn = 3600) {
const client = getMeilisearchClient();
const searchRules = {
[INDEX_NAME]: {
filter: `userId = ${userId} OR organizationId IN [${organizationIds.join(', ')}]`
}
};
const expiresAt = new Date(Date.now() + expiresIn * 1000);
return client.generateTenantToken(searchRules, {
apiKey: MEILISEARCH_MASTER_KEY,
expiresAt
});
}