API, Backend & Frontend
This commit is contained in:
727
backend/server.js
Normal file
727
backend/server.js
Normal file
@@ -0,0 +1,727 @@
|
||||
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}`);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user