require ('dotenv').config(); const express = require('express'); const {getEnv} = require('@nexusai/shared'); const { getDB } = require('./db'); const episodic = require('./episodic'); const semantic = require('./semantic'); const entities = require('./entities'); const app = express(); app.use(express.json()); const PORT = getEnv('PORT', '3002'); // Default to 3002 if PORT is not set //initialize database on startup const db = getDB(); semantic.initCollections() .then(() => console.log(`QDrant collections ready`)) .catch(err => console.error(`QDrant initialization error:`, err.message)); // Health check endpoint app.get('/health', (req, res) => { res.json({ service: 'Memory Service', status: 'healthy' }); }); /************************************ */ /********** Session Routes ********** */ /************************************ */ // Creates a new session with an external ID and optional metadata app.post('/sessions', (req, res) => { const {externalId, metadata} = req.body; if (!externalId) { return res.status(400).json({ error: 'externalId is required' }); } try { const session = episodic.createSession(externalId, metadata); res.status(201).json(session); } catch (err) { res.status(409).json({ error: 'Session already exists', detail: err.message }); } }); // Retrieves a session by its internal ID app.get('/sessions/:id', (req, res) => { const session = episodic.getSession(req.params.id); if (!session) return res.status(404).json({ error: 'Session not found' }); res.json(session); }); // Retrieves a session by its external ID app.get('/sessions/by-external/:externalId', (req, res) => { const session = episodic.getSessionByExternalId(req.params.externalId); if (!session) return res.status(404).json({ error: 'Session not found' }); res.json(session); }); // Updates the session's updated_at timestamp to now app.delete('/sessions/:id', (req, res) => { episodic.deleteSession(req.params.id); res.status(204).send(); }); /************************************* */ /********** Episodic Routes ********** */ /************************************* */ app.post('/episodes', async (req, res) => { const { sessionId, userMessage, aiResponse, tokenCount, metadata } = req.body; if (!sessionId || !userMessage || !aiResponse) { return res.status(400).json({ error: 'sessionId, userMessage and aiResponse are required' }); } const episode = await episodic.createEpisode(sessionId, userMessage, aiResponse, tokenCount, metadata); res.status(201).json(episode); }); // Search MUST come before /:id — otherwise 'search' gets captured as an id app.get('/episodes/search', (req, res) => { const { q, limit = 10 } = req.query; if (!q) return res.status(400).json({ error: 'q (query) parameter is required' }); const results = episodic.searchEpisodes(q, Number(limit)); res.json(results); }); app.get('/episodes/:id', (req, res) => { const episode = episodic.getEpisode(req.params.id); if (!episode) return res.status(404).json({ error: 'Episode not found' }); res.json(episode); }); app.delete('/episodes/:id', (req, res) => { episodic.deleteEpisode(req.params.id); res.status(204).send(); }); /*********************************** */ /********** Entity Routes ********** */ /*********************************** */ //Upsert an entity, creates or updates if already exists app.post('/entities', (req, res) => { const {name, type, notes, metadata} = req.body; if (!name || !type) { return res.status(400).json({ error: 'name and type are required' }); } const entity = entities.upsertEntity(name, type, notes, metadata); res.status(201).json(entity); }); // Get an entity by ID app.get('/entities/:id', (req, res) => { const entity = entities.getEntity(req.params.id); if (!entity) return res.status(404).json({ error: 'Entity not found' }); res.json(entity); }); // Get all entities of a given type app.get('/entities/by-type/:type', (req, res) => { res.json(entities.getEntitiesByType(req.params.type)); }); // Delete an entity by ID app.delete('/entities/:id', (req, res) => { entities.deleteEntity(req.params.id); res.status(204).send(); }); /***************************************** */ /********** Relationship Routes ********** */ /***************************************** */ // Upsert a relationship between two entities app.post('/relationships', (req, res) => { const {fromId, toId, label, metadata } = req.body; if (!fromId || !toId || !label) { return res.status(400).json({ error: 'fromId, toId and label are required' }); } const relationship = entities.upsertRelationship(fromId, toId, label, metadata); res.status(201).json(relationship); }); // Get all relationships for a given entity ID app.get('/entities/:id/relationships', (req, res) => { res.json(entities.getRelationshipsByEntity(req.params.id)); }); // Delete a specific relationship app.delete('/relationships', (req, res) => { const {fromId, toId, label} = req.body; if (!fromId || !toId || !label) { return res.status(400).json({ error: 'fromId, toId and label are required' }); } entities.deleteRelationship(fromId, toId, label); res.status(204).send(); }) /********************************** */ /********** Start Server ********** */ /********************************** */ app.listen(PORT, () => { console.log(`Memory Service is running on port ${PORT}`); });