728 lines
22 KiB
JavaScript
728 lines
22 KiB
JavaScript
import express from 'express';
|
|
import cors from 'cors';
|
|
import sqlite3 from 'sqlite3';
|
|
import { promisify } from 'util';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import session from 'express-session';
|
|
import bcrypt from 'bcrypt';
|
|
import { searchMedicines, getMedicineDetails } from './cima-service.js';
|
|
import { runFarmaciaWebhookImport, DEFAULT_FARMACIAS_WEBHOOK, importPharmaciesFromRows } from './farmacias-webhook-import.js';
|
|
import { fetchPharmaciesExternal } from '../API/index.js';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3001;
|
|
|
|
// Configure CORS with credentials
|
|
app.use(cors({
|
|
origin: 'http://localhost:3000',
|
|
credentials: true
|
|
}));
|
|
app.use(express.json());
|
|
|
|
// Configure session
|
|
app.use(session({
|
|
secret: process.env.SESSION_SECRET || 'farma-finder-secret-key-change-in-production',
|
|
resave: false,
|
|
saveUninitialized: false,
|
|
cookie: {
|
|
secure: false, // Set to true in production with HTTPS
|
|
httpOnly: true,
|
|
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
|
}
|
|
}));
|
|
|
|
// Database setup
|
|
const dbPath = path.join(__dirname, 'database.sqlite');
|
|
const db = new sqlite3.Database(dbPath);
|
|
|
|
// Custom wrapper to get lastID from db.run
|
|
function dbRun(sql, params = []) {
|
|
return new Promise((resolve, reject) => {
|
|
db.run(sql, params, function(err) {
|
|
if (err) reject(err);
|
|
else resolve({ lastID: this.lastID, changes: this.changes });
|
|
});
|
|
});
|
|
}
|
|
|
|
const dbAll = promisify(db.all.bind(db));
|
|
const dbGet = promisify(db.get.bind(db));
|
|
|
|
// Initialize database tables
|
|
async function initDatabase() {
|
|
try {
|
|
// Create pharmacies table
|
|
await dbRun(`
|
|
CREATE TABLE IF NOT EXISTS pharmacies (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL,
|
|
address TEXT NOT NULL,
|
|
phone TEXT,
|
|
latitude REAL,
|
|
longitude REAL
|
|
)
|
|
`);
|
|
|
|
// Create medicines table
|
|
await dbRun(`
|
|
CREATE TABLE IF NOT EXISTS medicines (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL,
|
|
active_ingredient TEXT,
|
|
dosage TEXT,
|
|
form TEXT
|
|
)
|
|
`);
|
|
|
|
// Create junction table for pharmacy-medicine relationships
|
|
// Ahora usa nregistro (número de registro de CIMA) en lugar de medicine_id local
|
|
await dbRun(`
|
|
CREATE TABLE IF NOT EXISTS pharmacy_medicines (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
pharmacy_id INTEGER NOT NULL,
|
|
medicine_nregistro TEXT NOT NULL,
|
|
medicine_name TEXT,
|
|
price REAL,
|
|
stock INTEGER DEFAULT 0,
|
|
FOREIGN KEY (pharmacy_id) REFERENCES pharmacies(id),
|
|
UNIQUE(pharmacy_id, medicine_nregistro)
|
|
)
|
|
`);
|
|
|
|
// Create indexes for better search performance
|
|
await dbRun(`CREATE INDEX IF NOT EXISTS idx_medicine_name ON medicines(name)`);
|
|
await dbRun(`CREATE INDEX IF NOT EXISTS idx_pharmacy_medicine ON pharmacy_medicines(medicine_nregistro)`);
|
|
|
|
// Create users table for authentication
|
|
await dbRun(`
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT UNIQUE NOT NULL,
|
|
password_hash TEXT NOT NULL,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`);
|
|
|
|
console.log('Database initialized successfully');
|
|
} catch (error) {
|
|
console.error('Error initializing database:', error);
|
|
}
|
|
}
|
|
|
|
// API Routes
|
|
|
|
// Search medicines using CIMA API with Redis cache
|
|
app.get('/api/medicines/search', async (req, res) => {
|
|
try {
|
|
const query = req.query.q || '';
|
|
|
|
if (!query.trim()) {
|
|
return res.json([]);
|
|
}
|
|
|
|
// Usar el servicio de CIMA con caché de Redis
|
|
const medicines = await searchMedicines(query);
|
|
|
|
res.json(medicines);
|
|
} catch (error) {
|
|
console.error('Error searching medicines:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
// Get pharmacies that sell a specific medicine (usando nregistro de CIMA)
|
|
app.get('/api/medicines/:medicineId/pharmacies', async (req, res) => {
|
|
try {
|
|
const nregistro = req.params.medicineId; // Ahora es el nregistro de CIMA
|
|
|
|
const pharmacies = await dbAll(`
|
|
SELECT
|
|
p.id,
|
|
p.name,
|
|
p.address,
|
|
p.phone,
|
|
p.latitude,
|
|
p.longitude,
|
|
pm.price,
|
|
pm.stock
|
|
FROM pharmacies p
|
|
INNER JOIN pharmacy_medicines pm ON p.id = pm.pharmacy_id
|
|
WHERE pm.medicine_nregistro = ?
|
|
ORDER BY p.name
|
|
`, [nregistro]);
|
|
|
|
res.json(pharmacies);
|
|
} catch (error) {
|
|
console.error('Error fetching pharmacies:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
// Get medicine details from CIMA API (usando nregistro)
|
|
app.get('/api/medicines/:medicineId', async (req, res) => {
|
|
try {
|
|
const nregistro = req.params.medicineId; // Ahora es el nregistro de CIMA
|
|
|
|
const medicine = await getMedicineDetails(nregistro);
|
|
|
|
if (!medicine) {
|
|
return res.status(404).json({ error: 'Medicine not found' });
|
|
}
|
|
|
|
res.json(medicine);
|
|
} catch (error) {
|
|
console.error('Error fetching medicine:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
// Get all pharmacies (for admin/debugging)
|
|
app.get('/api/pharmacies', async (req, res) => {
|
|
try {
|
|
const pharmacies = await dbAll(`
|
|
SELECT * FROM pharmacies ORDER BY name
|
|
`);
|
|
res.json(pharmacies);
|
|
} catch (error) {
|
|
console.error('Error fetching pharmacies:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
// ========== AUTHENTICATION MIDDLEWARE ==========
|
|
|
|
// Middleware to check if user is authenticated
|
|
const requireAuth = (req, res, next) => {
|
|
if (req.session && req.session.userId) {
|
|
return next();
|
|
}
|
|
return res.status(401).json({ error: 'Authentication required' });
|
|
};
|
|
|
|
// ========== AUTHENTICATION ROUTES ==========
|
|
|
|
// Login endpoint
|
|
app.post('/api/auth/login', async (req, res) => {
|
|
try {
|
|
const { username, password } = req.body;
|
|
|
|
if (!username || !password) {
|
|
return res.status(400).json({ error: 'Username and password are required' });
|
|
}
|
|
|
|
// Find user by username
|
|
const user = await dbGet(
|
|
'SELECT * FROM users WHERE username = ?',
|
|
[username.trim()]
|
|
);
|
|
|
|
if (!user) {
|
|
return res.status(401).json({ error: 'Invalid username or password' });
|
|
}
|
|
|
|
// Verify password
|
|
const isValidPassword = await bcrypt.compare(password, user.password_hash);
|
|
|
|
if (!isValidPassword) {
|
|
return res.status(401).json({ error: 'Invalid username or password' });
|
|
}
|
|
|
|
// Create session
|
|
req.session.userId = user.id;
|
|
req.session.username = user.username;
|
|
|
|
res.json({
|
|
message: 'Login successful',
|
|
user: {
|
|
id: user.id,
|
|
username: user.username
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Error during login:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
// Logout endpoint
|
|
app.post('/api/auth/logout', (req, res) => {
|
|
req.session.destroy((err) => {
|
|
if (err) {
|
|
console.error('Error destroying session:', err);
|
|
return res.status(500).json({ error: 'Error logging out' });
|
|
}
|
|
res.json({ message: 'Logout successful' });
|
|
});
|
|
});
|
|
|
|
// Check authentication status
|
|
app.get('/api/auth/check', (req, res) => {
|
|
if (req.session && req.session.userId) {
|
|
res.json({
|
|
authenticated: true,
|
|
user: {
|
|
id: req.session.userId,
|
|
username: req.session.username
|
|
}
|
|
});
|
|
} else {
|
|
res.json({ authenticated: false });
|
|
}
|
|
});
|
|
|
|
// ========== ADMIN API ROUTES ==========
|
|
|
|
/** Suggested search radius (m) from Nominatim bounding box [south, north, west, east] */
|
|
function radiusMetersFromBoundingBox(south, north, west, east) {
|
|
const s = parseFloat(south);
|
|
const n = parseFloat(north);
|
|
const w = parseFloat(west);
|
|
const e = parseFloat(east);
|
|
if (![s, n, w, e].every(Number.isFinite)) return null;
|
|
const latMid = (s + n) / 2;
|
|
const latM = Math.abs(n - s) * 111320;
|
|
const lonM = Math.abs(e - w) * 111320 * Math.cos((latMid * Math.PI) / 180);
|
|
const half = (Math.max(latM, lonM) / 2) * 1.12;
|
|
return Math.round(Math.min(Math.max(half, 1500), 50000));
|
|
}
|
|
|
|
async function nominatimSearchFirstHit(originalQuery) {
|
|
const trimmed = originalQuery.trim();
|
|
const variants = [
|
|
trimmed,
|
|
`${trimmed}, España`,
|
|
`${trimmed}, Spain`,
|
|
`${trimmed}, ES`,
|
|
];
|
|
const seen = new Set();
|
|
const uniqueVariants = variants.filter((v) => {
|
|
const k = v.toLowerCase();
|
|
if (seen.has(k)) return false;
|
|
seen.add(k);
|
|
return true;
|
|
});
|
|
|
|
const delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
|
for (let i = 0; i < uniqueVariants.length; i++) {
|
|
const q = uniqueVariants[i];
|
|
if (i > 0) await delay(1100);
|
|
const params = new URLSearchParams({
|
|
format: 'json',
|
|
limit: '1',
|
|
q,
|
|
addressdetails: '0',
|
|
});
|
|
const url = `https://nominatim.openstreetmap.org/search?${params}`;
|
|
const nomRes = await fetch(url, {
|
|
headers: {
|
|
Accept: 'application/json',
|
|
'Accept-Language': 'es,en',
|
|
'User-Agent':
|
|
'FarmaFinder/1.0 (pharmacy admin; geocoding; https://github.com/)',
|
|
},
|
|
});
|
|
const text = await nomRes.text();
|
|
let data;
|
|
try {
|
|
data = text ? JSON.parse(text) : [];
|
|
} catch {
|
|
return { ok: false, error: 'Geocoder returned invalid JSON', status: 502 };
|
|
}
|
|
if (!nomRes.ok) {
|
|
return {
|
|
ok: false,
|
|
error: `Geocoder HTTP ${nomRes.status}`,
|
|
status: 502,
|
|
};
|
|
}
|
|
if (Array.isArray(data) && data.length > 0) {
|
|
return { ok: true, hit: data[0], triedQuery: q };
|
|
}
|
|
}
|
|
|
|
|
|
return {
|
|
ok: false,
|
|
error:
|
|
'No place found. Try adding the region (e.g. "Rubí, Barcelona" or "Toledo, Spain").',
|
|
status: 422,
|
|
};
|
|
}
|
|
|
|
// Geocode city → lat, lon, radius (OpenStreetMap Nominatim; admin-only, low volume)
|
|
app.get('/api/admin/geocode', requireAuth, async (req, res) => {
|
|
const q = (req.query.q || '').trim();
|
|
if (!q) {
|
|
return res.status(400).json({ error: 'Query parameter q is required' });
|
|
}
|
|
try {
|
|
const result = await nominatimSearchFirstHit(q);
|
|
if (!result.ok) {
|
|
return res.status(result.status).json({ error: result.error });
|
|
}
|
|
const hit = result.hit;
|
|
const lat = parseFloat(hit.lat);
|
|
const lon = parseFloat(hit.lon);
|
|
if (!Number.isFinite(lat) || !Number.isFinite(lon)) {
|
|
return res.status(502).json({ error: 'Geocoder result missing coordinates' });
|
|
}
|
|
let radius = 12000;
|
|
if (hit.boundingbox && hit.boundingbox.length >= 4) {
|
|
const r = radiusMetersFromBoundingBox(
|
|
hit.boundingbox[0],
|
|
hit.boundingbox[1],
|
|
hit.boundingbox[2],
|
|
hit.boundingbox[3]
|
|
);
|
|
if (r != null) radius = r;
|
|
}
|
|
res.json({
|
|
lat,
|
|
lon,
|
|
radius,
|
|
displayName: hit.display_name || q,
|
|
matchedQuery: result.triedQuery,
|
|
});
|
|
} catch (err) {
|
|
console.error('Geocode error:', err);
|
|
res.status(500).json({ error: err.message || 'Geocode failed' });
|
|
}
|
|
});
|
|
|
|
// Add a new pharmacy
|
|
app.post('/api/admin/pharmacies', requireAuth, async (req, res) => {
|
|
try {
|
|
const { name, address, phone, latitude, longitude } = req.body;
|
|
|
|
if (!name || !address) {
|
|
return res.status(400).json({ error: 'Name and address are required' });
|
|
}
|
|
|
|
// Check for duplicate (same name and address)
|
|
const existing = await dbGet(
|
|
'SELECT * FROM pharmacies WHERE name = ? AND address = ?',
|
|
[name.trim(), address.trim()]
|
|
);
|
|
|
|
if (existing) {
|
|
return res.status(400).json({ error: 'A pharmacy with this name and address already exists' });
|
|
}
|
|
|
|
const result = await dbRun(
|
|
'INSERT INTO pharmacies (name, address, phone, latitude, longitude) VALUES (?, ?, ?, ?, ?)',
|
|
[name.trim(), address.trim(), phone ? phone.trim() : null, latitude || null, longitude || null]
|
|
);
|
|
|
|
if (!result || result.lastID === undefined) {
|
|
throw new Error('Failed to get lastID from database insert');
|
|
}
|
|
|
|
const newPharmacy = await dbGet(
|
|
'SELECT * FROM pharmacies WHERE id = ?',
|
|
[result.lastID]
|
|
);
|
|
|
|
if (!newPharmacy) {
|
|
throw new Error('Failed to retrieve created pharmacy');
|
|
}
|
|
|
|
res.status(201).json(newPharmacy);
|
|
} catch (error) {
|
|
console.error('Error adding pharmacy:', error);
|
|
if (error.message.includes('UNIQUE constraint')) {
|
|
res.status(400).json({ error: 'A pharmacy with this information already exists' });
|
|
} else {
|
|
res.status(500).json({ error: error.message || 'Internal server error' });
|
|
}
|
|
}
|
|
});
|
|
|
|
// Update a pharmacy
|
|
app.put('/api/admin/pharmacies/:id', requireAuth, async (req, res) => {
|
|
try {
|
|
const pharmacyId = parseInt(req.params.id);
|
|
const { name, address, phone, latitude, longitude } = req.body;
|
|
|
|
if (!name || !address) {
|
|
return res.status(400).json({ error: 'Name and address are required' });
|
|
}
|
|
|
|
await dbRun(
|
|
'UPDATE pharmacies SET name = ?, address = ?, phone = ?, latitude = ?, longitude = ? WHERE id = ?',
|
|
[name, address, phone || null, latitude || null, longitude || null, pharmacyId]
|
|
);
|
|
|
|
const updatedPharmacy = await dbGet(
|
|
'SELECT * FROM pharmacies WHERE id = ?',
|
|
[pharmacyId]
|
|
);
|
|
|
|
if (!updatedPharmacy) {
|
|
return res.status(404).json({ error: 'Pharmacy not found' });
|
|
}
|
|
|
|
res.json(updatedPharmacy);
|
|
} catch (error) {
|
|
console.error('Error updating pharmacy:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
// Delete a pharmacy
|
|
app.delete('/api/admin/pharmacies/:id', requireAuth, async (req, res) => {
|
|
try {
|
|
const pharmacyId = parseInt(req.params.id);
|
|
|
|
// Delete related pharmacy_medicines first
|
|
await dbRun('DELETE FROM pharmacy_medicines WHERE pharmacy_id = ?', [pharmacyId]);
|
|
|
|
// Delete the pharmacy
|
|
await dbRun('DELETE FROM pharmacies WHERE id = ?', [pharmacyId]);
|
|
|
|
res.json({ message: 'Pharmacy deleted successfully' });
|
|
} catch (error) {
|
|
console.error('Error deleting pharmacy:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
// Import pharmacies from webhook (e.g. n8n).
|
|
// Body: { "url"?: string, "lat"?, "lon"|"lng"?, "radio"? } — lat/lon/radio add ?lat=&lon=&radio= (metres)
|
|
app.post('/api/admin/pharmacies/import-webhook', requireAuth, async (req, res) => {
|
|
try {
|
|
const body = req.body && typeof req.body === 'object' ? req.body : {};
|
|
const url =
|
|
(typeof body.url === 'string' && body.url.trim()) ||
|
|
process.env.FARMACIAS_WEBHOOK_URL ||
|
|
DEFAULT_FARMACIAS_WEBHOOK;
|
|
const region = {};
|
|
if (body.lat != null && String(body.lat).trim() !== '') region.lat = body.lat;
|
|
const lonVal = body.lon ?? body.lng;
|
|
if (lonVal != null && String(lonVal).trim() !== '') region.lon = lonVal;
|
|
if (body.radio != null && String(body.radio).trim() !== '') region.radio = body.radio;
|
|
const hasRegion =
|
|
region.lat != null || region.lon != null || region.radio != null;
|
|
const result = await runFarmaciaWebhookImport(
|
|
dbGet,
|
|
dbRun,
|
|
url.trim(),
|
|
hasRegion ? region : null
|
|
);
|
|
res.json(result);
|
|
} catch (error) {
|
|
console.error('Webhook pharmacy import:', error);
|
|
const status = error.message?.includes('HTTP') ? 502 : 400;
|
|
res.status(status).json({
|
|
error: error.message || 'Webhook import failed',
|
|
...(error.details ? { details: error.details } : {}),
|
|
});
|
|
}
|
|
});
|
|
|
|
// Import from OpenStreetMap (Overpass), or a JSON open-data URL — see /API
|
|
app.post('/api/admin/pharmacies/import-external', requireAuth, async (req, res) => {
|
|
try {
|
|
const body = req.body && typeof req.body === 'object' ? req.body : {};
|
|
const source = body.source;
|
|
if (!['osm', 'openData'].includes(source)) {
|
|
return res
|
|
.status(400)
|
|
.json({ error: 'source must be "osm", or "openData"' });
|
|
}
|
|
|
|
const rows = await fetchPharmaciesExternal({
|
|
source,
|
|
lat: body.lat,
|
|
lon: body.lon ?? body.lng,
|
|
lng: body.lng,
|
|
radio: body.radio,
|
|
openDataUrl:
|
|
typeof body.openDataUrl === 'string' ? body.openDataUrl.trim() : undefined,
|
|
});
|
|
|
|
if (!rows.length) {
|
|
return res.json({
|
|
inserted: 0,
|
|
skipped: 0,
|
|
invalid: 0,
|
|
errors: [],
|
|
totalReceived: 0,
|
|
source,
|
|
message: 'No pharmacies returned for this query',
|
|
});
|
|
}
|
|
|
|
const stats = await importPharmaciesFromRows(dbGet, dbRun, rows);
|
|
res.json({ ...stats, totalReceived: rows.length, source });
|
|
} catch (error) {
|
|
console.error('External pharmacy import:', error);
|
|
res.status(400).json({ error: error.message || 'External import failed' });
|
|
}
|
|
});
|
|
|
|
// Search medicines from CIMA API (para el admin)
|
|
app.get('/api/admin/medicines', requireAuth, async (req, res) => {
|
|
try {
|
|
const query = req.query.q || '';
|
|
|
|
if (!query.trim()) {
|
|
// Si no hay query, retornar lista vacía o medicamentos populares
|
|
return res.json([]);
|
|
}
|
|
|
|
// Usar el servicio de CIMA
|
|
const medicines = await searchMedicines(query);
|
|
res.json(medicines);
|
|
} catch (error) {
|
|
console.error('Error fetching medicines:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
// NOTA: Ya no necesitamos endpoints para crear/editar medicamentos localmente
|
|
// porque ahora usamos la API de CIMA como fuente de verdad
|
|
|
|
// Add a new medicine (DEPRECATED - mantenido solo para compatibilidad)
|
|
app.post('/api/admin/medicines', requireAuth, async (req, res) => {
|
|
try {
|
|
// Ya no se agregan medicamentos localmente, se obtienen de CIMA
|
|
return res.status(400).json({
|
|
error: 'Medicine management has been moved to CIMA API. Use the search feature to find medicines.'
|
|
});
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
// Update a medicine (DEPRECATED - mantenido solo para compatibilidad)
|
|
app.put('/api/admin/medicines/:id', requireAuth, async (req, res) => {
|
|
try {
|
|
return res.status(400).json({
|
|
error: 'Medicine management has been moved to CIMA API.'
|
|
});
|
|
} catch (error) {
|
|
console.error('Error updating medicine:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
// Get medicines for a specific pharmacy (usando nregistro)
|
|
app.get('/api/admin/pharmacies/:pharmacyId/medicines', requireAuth, async (req, res) => {
|
|
try {
|
|
const pharmacyId = parseInt(req.params.pharmacyId);
|
|
|
|
const medicines = await dbAll(`
|
|
SELECT
|
|
pm.id,
|
|
pm.pharmacy_id,
|
|
pm.medicine_nregistro,
|
|
pm.medicine_name,
|
|
pm.price,
|
|
pm.stock
|
|
FROM pharmacy_medicines pm
|
|
WHERE pm.pharmacy_id = ?
|
|
ORDER BY pm.medicine_name
|
|
`, [pharmacyId]);
|
|
|
|
res.json(medicines);
|
|
} catch (error) {
|
|
console.error('Error fetching pharmacy medicines:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
// Add medicine to a pharmacy (or update if exists) usando nregistro de CIMA
|
|
app.post('/api/admin/pharmacy-medicines', requireAuth, async (req, res) => {
|
|
try {
|
|
const { pharmacy_id, medicine_nregistro, medicine_name, price, stock } = req.body;
|
|
|
|
if (!pharmacy_id || !medicine_nregistro) {
|
|
return res.status(400).json({ error: 'Pharmacy ID and Medicine nregistro are required' });
|
|
}
|
|
|
|
// Check if relationship already exists
|
|
const existing = await dbGet(
|
|
'SELECT * FROM pharmacy_medicines WHERE pharmacy_id = ? AND medicine_nregistro = ?',
|
|
[pharmacy_id, medicine_nregistro]
|
|
);
|
|
|
|
if (existing) {
|
|
// Update existing relationship
|
|
await dbRun(
|
|
'UPDATE pharmacy_medicines SET medicine_name = ?, price = ?, stock = ? WHERE pharmacy_id = ? AND medicine_nregistro = ?',
|
|
[medicine_name, price || null, stock || 0, pharmacy_id, medicine_nregistro]
|
|
);
|
|
} else {
|
|
// Insert new relationship
|
|
await dbRun(
|
|
'INSERT INTO pharmacy_medicines (pharmacy_id, medicine_nregistro, medicine_name, price, stock) VALUES (?, ?, ?, ?, ?)',
|
|
[pharmacy_id, medicine_nregistro, medicine_name, price || null, stock || 0]
|
|
);
|
|
}
|
|
|
|
const relationship = await dbGet(
|
|
`SELECT * FROM pharmacy_medicines
|
|
WHERE pharmacy_id = ? AND medicine_nregistro = ?`,
|
|
[pharmacy_id, medicine_nregistro]
|
|
);
|
|
|
|
res.status(201).json(relationship);
|
|
} catch (error) {
|
|
console.error('Error adding medicine to pharmacy:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
// Update pharmacy-medicine relationship
|
|
app.put('/api/admin/pharmacy-medicines/:id', requireAuth, async (req, res) => {
|
|
try {
|
|
const id = parseInt(req.params.id);
|
|
const { price, stock } = req.body;
|
|
|
|
await dbRun(
|
|
'UPDATE pharmacy_medicines SET price = ?, stock = ? WHERE id = ?',
|
|
[price || null, stock || 0, id]
|
|
);
|
|
|
|
const updated = await dbGet(
|
|
'SELECT * FROM pharmacy_medicines WHERE id = ?',
|
|
[id]
|
|
);
|
|
|
|
if (!updated) {
|
|
return res.status(404).json({ error: 'Relationship not found' });
|
|
}
|
|
|
|
res.json(updated);
|
|
} catch (error) {
|
|
console.error('Error updating pharmacy-medicine:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
// Delete pharmacy-medicine relationship
|
|
app.delete('/api/admin/pharmacy-medicines/:id', requireAuth, async (req, res) => {
|
|
try {
|
|
const id = parseInt(req.params.id);
|
|
await dbRun('DELETE FROM pharmacy_medicines WHERE id = ?', [id]);
|
|
res.json({ message: 'Medicine removed from pharmacy successfully' });
|
|
} catch (error) {
|
|
console.error('Error deleting pharmacy-medicine:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
// Start server
|
|
initDatabase().then(() => {
|
|
app.listen(PORT, () => {
|
|
console.log(`Server running on http://localhost:${PORT}`);
|
|
});
|
|
});
|
|
|