diff --git a/src/index.ts b/src/index.ts index 014c123..a88ef44 100644 --- a/src/index.ts +++ b/src/index.ts @@ -57,7 +57,9 @@ app.use('/v1/rx', prescriptionRouter); app.use('/v1/prescriptions', prescriptionRouter); app.use('/v1/ai', aiRouter); app.use('/v1/integration', integrationRouter); +app.use('/integration', integrationRouter); app.use('/v1/patients', integrationRouter); +app.use('/patients', integrationRouter); app.use('/v1/upload', uploadRouter); app.use(viewerRouter); diff --git a/src/middleware/auth.ts b/src/middleware/auth.ts index c9ac140..d582de6 100644 --- a/src/middleware/auth.ts +++ b/src/middleware/auth.ts @@ -16,7 +16,7 @@ declare global { export const requireApiKey: RequestHandler = (req: Request, res: Response, next: NextFunction): void => { // Allow /v1/integration, /v1/patients, /health, and /db-viewer routes to validate via their own auth or be publicly checked - if (req.originalUrl.startsWith('/v1/integration') || req.originalUrl.startsWith('/v1/patients') || req.originalUrl === '/health' || req.originalUrl.startsWith('/health') || req.originalUrl.startsWith('/db-viewer') || req.originalUrl.startsWith('/api/db')) { + if (req.originalUrl.startsWith('/v1/integration') || req.originalUrl.startsWith('/integration') || req.originalUrl.startsWith('/v1/patients') || req.originalUrl.startsWith('/patients') || req.originalUrl === '/health' || req.originalUrl.startsWith('/health') || req.originalUrl.startsWith('/db-viewer') || req.originalUrl.startsWith('/api/db')) { return next(); } diff --git a/src/modules/integration/controller.ts b/src/modules/integration/controller.ts index 6dfb8ac..714dcdd 100644 --- a/src/modules/integration/controller.ts +++ b/src/modules/integration/controller.ts @@ -8,9 +8,15 @@ export class IntegrationController { try { const payload = req.body; + const patientId = payload.hospital_patient_id || payload.patient_id || payload.patientId || payload.id; + const fullName = payload.full_name || payload.patient_name || payload.patientName || payload.name; + // Basic validation - if (!payload.hospital_patient_id || !payload.full_name) { - return res.status(400).json({ error: 'Missing required patient fields' }); + if (!patientId || !fullName) { + return res.status(400).json({ + error: 'Missing required patient fields', + details: 'Please include hospital_patient_id (or patient_id) and full_name (or name) in your JSON request body.' + }); } // Save or update the patient in Pharmacy DB @@ -29,9 +35,41 @@ export class IntegrationController { // Endpoint to fetch all patients synced to Pharmacy DB static async getAllPatients(req: Request, res: Response) { try { - const tenantId = req.headers['x-tenant-id'] || req.tenantId || 'act_admin'; - const hospitalId = req.headers['x-hospital-id'] || req.hospitalId || '1'; - const patients = await IntegrationService.getAllPatients(String(tenantId), String(hospitalId)); + let tenantId = (req.body?.tenant_id || req.body?.tenantId || req.query?.tenant_id || req.query?.tenantId || req.headers['x-tenant-id'] || req.tenantId || req.body?.hospital_id || req.body?.hospitalId || req.query?.hospital_id || req.query?.hospitalId || req.headers['x-hospital-id'] || req.hospitalId) as string | undefined; + let hospitalId = (req.body?.hospital_id || req.body?.hospitalId || req.query?.hospital_id || req.query?.hospitalId || req.headers['x-hospital-id'] || req.hospitalId) as string | undefined; + + // Extract from Bearer token if headers not explicitly passed + if (req.headers.authorization) { + try { + const token = req.headers.authorization.replace('Bearer ', '').trim(); + const jwt = require('jsonwebtoken'); + const secret = process.env.JWT_SECRET || 'super_secret_pharmacy_key_2026'; + const decoded = jwt.verify(token, secret) as any; + if (decoded) { + tenantId = (tenantId && tenantId !== 'act_admin') ? tenantId : (decoded.tenant_id || decoded.tenantId || tenantId); + hospitalId = (hospitalId && hospitalId !== 'undefined' && hospitalId !== 'null') ? hospitalId : (decoded.hospital_id || decoded.hospitalId || hospitalId); + } + } catch (e) {} + } + + // If tenantId is a branch tenant_id (e.g. 'pharm_mkkhospital_...'), fetch its parent hospital_id from users DB + if (tenantId && tenantId.startsWith('pharm_') && (!hospitalId || hospitalId === 'undefined' || hospitalId === 'null')) { + try { + const userRes = await require('../../db/pool').pool.query( + 'SELECT hospital_id FROM users WHERE tenant_id = $1 LIMIT 1', + [tenantId] + ); + if (userRes.rows.length > 0 && userRes.rows[0].hospital_id) { + hospitalId = String(userRes.rows[0].hospital_id); + } + } catch (e) {} + } + + const patients = await IntegrationService.getAllPatients( + tenantId ? String(tenantId) : '', + hospitalId ? String(hospitalId) : undefined + ); + res.status(200).json({ message: 'Patients fetched successfully', data: patients @@ -159,18 +197,43 @@ export class IntegrationController { static async getAllPrescriptions(req: Request, res: Response) { try { - const tenantId = req.headers['x-tenant-id'] || req.tenantId || 'act_admin'; - const hospitalId = req.headers['x-hospital-id'] || req.hospitalId || '1'; + let tenantId = (req.body?.tenant_id || req.body?.tenantId || req.query?.tenant_id || req.query?.tenantId || req.headers['x-tenant-id'] || req.tenantId || req.body?.hospital_id || req.body?.hospitalId || req.query?.hospital_id || req.query?.hospitalId || req.headers['x-hospital-id'] || req.hospitalId) as string | undefined; + let hospitalId = (req.body?.hospital_id || req.body?.hospitalId || req.query?.hospital_id || req.query?.hospitalId || req.headers['x-hospital-id'] || req.hospitalId) as string | undefined; + + if (req.headers.authorization) { + try { + const token = req.headers.authorization.replace('Bearer ', '').trim(); + const jwt = require('jsonwebtoken'); + const secret = process.env.JWT_SECRET || 'super_secret_pharmacy_key_2026'; + const decoded = jwt.verify(token, secret) as any; + if (decoded) { + tenantId = (tenantId && tenantId !== 'act_admin') ? tenantId : (decoded.tenant_id || decoded.tenantId || tenantId); + hospitalId = (hospitalId && hospitalId !== 'undefined' && hospitalId !== 'null') ? hospitalId : (decoded.hospital_id || decoded.hospitalId || hospitalId); + } + } catch (e) {} + } + + if (tenantId && tenantId.startsWith('pharm_') && (!hospitalId || hospitalId === 'undefined' || hospitalId === 'null')) { + try { + const clientPool = require('../../db/pool').pool; + const r = await clientPool.query('SELECT hospital_id FROM users WHERE tenant_id = $1 LIMIT 1', [tenantId]); + if (r.rows.length > 0 && r.rows[0].hospital_id) { + hospitalId = String(r.rows[0].hospital_id); + } + } catch (e) {} + } + const client = await require('../../db/pool').pool.connect(); try { let query: string; let values: any[]; - if (!tenantId || tenantId === 'act_admin' || tenantId === 'all' || tenantId === 'undefined' || tenantId === 'null') { + if (!tenantId || tenantId === 'all' || tenantId === 'superadmin' || tenantId === 'undefined' || tenantId === 'null') { query = `SELECT * FROM prescriptions ORDER BY created_at DESC;`; values = []; } else { - query = `SELECT * FROM prescriptions WHERE tenant_id = $1 OR tenant_id = $2 OR tenant_id = '1' OR tenant_id = 'act_admin' OR tenant_id IS NULL ORDER BY created_at DESC;`; - values = [String(tenantId), String(hospitalId)]; + const hospId = hospitalId ? String(hospitalId) : String(tenantId); + query = `SELECT * FROM prescriptions WHERE tenant_id = $1 OR tenant_id = $2 ORDER BY created_at DESC;`; + values = [String(tenantId), hospId]; } const result = await client.query(query, values); res.status(200).json({ message: 'All prescriptions', data: result.rows }); diff --git a/src/modules/integration/service.ts b/src/modules/integration/service.ts index 469be64..4c180b9 100644 --- a/src/modules/integration/service.ts +++ b/src/modules/integration/service.ts @@ -13,35 +13,38 @@ export class IntegrationService { const allergies = payload.allergies ? (typeof payload.allergies === 'string' ? payload.allergies : JSON.stringify(payload.allergies)) : null; const tenantId = payload.hospital_id || payload.tenant_id || payload.tenantId ? String(payload.hospital_id || payload.tenant_id || payload.tenantId) : 'act_admin'; - console.log('[IntegrationService] Syncing patient from Hospital API:', hospitalPatientId, fullName); + console.log('[IntegrationService] Syncing patient from Hospital API:', hospitalPatientId, fullName, 'Tenant:', tenantId); const client = await pool.connect(); try { - const query = ` - INSERT INTO patients (hospital_patient_id, full_name, contact_number, email, allergies, tenant_id) - VALUES ($1, $2, $3, $4, $5, $6) - ON CONFLICT (hospital_patient_id) - DO UPDATE SET - full_name = EXCLUDED.full_name, - contact_number = COALESCE(EXCLUDED.contact_number, patients.contact_number), - email = COALESCE(EXCLUDED.email, patients.email), - allergies = COALESCE(EXCLUDED.allergies, patients.allergies), - tenant_id = EXCLUDED.tenant_id, - updated_at = CURRENT_TIMESTAMP - RETURNING id, hospital_patient_id, full_name; - `; - - const values = [ - hospitalPatientId, - fullName, - contactNumber, - email, - allergies, - tenantId - ]; + const checkRes = await client.query( + 'SELECT id FROM patients WHERE hospital_patient_id = $1 AND tenant_id = $2 LIMIT 1', + [hospitalPatientId, tenantId] + ); - const result = await client.query(query, values); - return result.rows[0]; + if (checkRes.rows.length > 0) { + const updateRes = await client.query( + `UPDATE patients + SET full_name = $1, + contact_number = COALESCE($2, contact_number), + email = COALESCE($3, email), + allergies = COALESCE($4, allergies), + updated_at = CURRENT_TIMESTAMP + WHERE hospital_patient_id = $5 AND tenant_id = $6 + RETURNING id, hospital_patient_id, full_name, tenant_id`, + [fullName, contactNumber, email, allergies, hospitalPatientId, tenantId] + ); + return updateRes.rows[0]; + } else { + const id = require('crypto').randomUUID(); + const insertRes = await client.query( + `INSERT INTO patients (id, hospital_patient_id, full_name, contact_number, email, allergies, tenant_id) + VALUES ($1, $2, $3, $4, $5, $6, $7) + RETURNING id, hospital_patient_id, full_name, tenant_id`, + [id, hospitalPatientId, fullName, contactNumber, email, allergies, tenantId] + ); + return insertRes.rows[0]; + } } finally { client.release(); } @@ -55,12 +58,12 @@ export class IntegrationService { try { let query: string; let values: any[]; - if (!tenantId || tenantId === 'act_admin' || tenantId === 'all' || tenantId === 'undefined' || tenantId === 'null') { + if (!tenantId || tenantId === 'all' || tenantId === 'superadmin' || tenantId === 'undefined' || tenantId === 'null') { query = `SELECT * FROM patients ORDER BY created_at DESC;`; values = []; } else { - const hospId = hospitalId || '1'; - query = `SELECT * FROM patients WHERE tenant_id = $1 OR tenant_id = $2 OR tenant_id = '1' OR tenant_id = 'act_admin' OR tenant_id IS NULL ORDER BY created_at DESC;`; + const hospId = hospitalId && hospitalId !== 'undefined' && hospitalId !== 'null' ? hospitalId : tenantId; + query = `SELECT * FROM patients WHERE tenant_id = $1 OR tenant_id = $2 ORDER BY created_at DESC;`; values = [tenantId, hospId]; } const result = await client.query(query, values); diff --git a/src/modules/prescription/service.ts b/src/modules/prescription/service.ts index 76a1327..7a2ab21 100644 --- a/src/modules/prescription/service.ts +++ b/src/modules/prescription/service.ts @@ -133,20 +133,32 @@ export class PrescriptionService { } async getPrescription(id: string, actorId: string, hospitalId?: string) { - const hospId = hospitalId || '1'; - const { rows } = await pool.query(`SELECT * FROM prescriptions WHERE id = $1 AND (tenant_id = $2 OR tenant_id = $3 OR tenant_id = '1' OR tenant_id = 'act_admin' OR tenant_id IS NULL)`, [id, actorId, hospId]); + const hospId = hospitalId || actorId; + if (!actorId || actorId === 'all' || actorId === 'superadmin' || actorId === 'undefined' || actorId === 'null') { + const { rows } = await pool.query(`SELECT * FROM prescriptions WHERE id = $1`, [id]); + return rows[0]; + } + const { rows } = await pool.query(`SELECT * FROM prescriptions WHERE id = $1 AND (tenant_id = $2 OR tenant_id = $3)`, [id, actorId, hospId]); return rows[0]; } async getAllPrescriptions(actorId: string, hospitalId?: string) { let query: string; let values: any[]; - if (!actorId || actorId === 'act_admin' || actorId === 'all' || actorId === 'undefined' || actorId === 'null') { + if (!actorId || actorId === 'all' || actorId === 'superadmin' || actorId === 'undefined' || actorId === 'null') { query = `SELECT * FROM prescriptions ORDER BY created_at DESC`; values = []; } else { - const hospId = hospitalId || '1'; - query = `SELECT * FROM prescriptions WHERE tenant_id = $1 OR tenant_id = $2 OR tenant_id = '1' OR tenant_id = 'act_admin' OR tenant_id IS NULL ORDER BY created_at DESC`; + let hospId = hospitalId || actorId; + if (actorId.startsWith('pharm_') && hospId === actorId) { + try { + const userRes = await pool.query('SELECT hospital_id FROM users WHERE tenant_id = $1 LIMIT 1', [actorId]); + if (userRes.rows.length > 0 && userRes.rows[0].hospital_id) { + hospId = String(userRes.rows[0].hospital_id); + } + } catch (e) {} + } + query = `SELECT * FROM prescriptions WHERE tenant_id = $1 OR tenant_id = $2 ORDER BY created_at DESC`; values = [actorId, hospId]; } const { rows } = await pool.query(query, values); @@ -154,12 +166,18 @@ export class PrescriptionService { } async confirmPrescription(id: string, confirmedData: any, actorId: string, hospitalId?: string) { - const hospId = hospitalId || '1'; - // Human Gate 1: Validate that unconfirmed fields are explicitly reviewed - const { rows } = await pool.query( - `SELECT status, extracted_data FROM prescriptions WHERE id = $1 AND (tenant_id = $2 OR tenant_id = $3 OR tenant_id = '1' OR tenant_id = 'act_admin' OR tenant_id IS NULL)`, - [id, actorId, hospId] - ); + const hospId = hospitalId || actorId; + let rows: any[]; + if (!actorId || actorId === 'all' || actorId === 'superadmin' || actorId === 'undefined' || actorId === 'null') { + const res = await pool.query(`SELECT status, extracted_data FROM prescriptions WHERE id = $1`, [id]); + rows = res.rows; + } else { + const res = await pool.query( + `SELECT status, extracted_data FROM prescriptions WHERE id = $1 AND (tenant_id = $2 OR tenant_id = $3)`, + [id, actorId, hospId] + ); + rows = res.rows; + } const record = rows[0]; if (!record) throw { status: 404, error: 'NOT_FOUND' }; @@ -184,11 +202,19 @@ export class PrescriptionService { } } - await pool.query(` - UPDATE prescriptions - SET status = 'confirmed', confirmed_data = $1 - WHERE id = $2 AND (tenant_id = $3 OR tenant_id = $4 OR tenant_id = '1' OR tenant_id = 'act_admin' OR tenant_id IS NULL) - `, [confirmedData, id, actorId, hospId]); + if (!actorId || actorId === 'all' || actorId === 'superadmin' || actorId === 'undefined' || actorId === 'null') { + await pool.query(` + UPDATE prescriptions + SET status = 'confirmed', confirmed_data = $1 + WHERE id = $2 + `, [confirmedData, id]); + } else { + await pool.query(` + UPDATE prescriptions + SET status = 'confirmed', confirmed_data = $1 + WHERE id = $2 AND (tenant_id = $3 OR tenant_id = $4) + `, [confirmedData, id, actorId, hospId]); + } return { id, status: 'confirmed' }; }