Missing files from commit
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* CLI: pull pharmacies from webhook and insert into database.sqlite
|
||||
* CLI: pull pharmacies from webhook and insert into database
|
||||
*
|
||||
* npm run import-farmacias
|
||||
* FARMACIAS_WEBHOOK_URL=https://... npm run import-farmacias
|
||||
@@ -10,12 +10,15 @@
|
||||
* node import-farmacias.js "https://n8n.example/webhook/farmacias" --lat 41.5631 --lon 2.0038 --radio 1500
|
||||
*
|
||||
* Env defaults for region: FARMACIAS_IMPORT_LAT, FARMACIAS_IMPORT_LON, FARMACIAS_IMPORT_RADIO
|
||||
*
|
||||
* Uses PostgreSQL when PG_URL is set, otherwise falls back to local SQLite.
|
||||
*/
|
||||
|
||||
import sqlite3 from 'sqlite3';
|
||||
import { promisify } from 'util';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import pg from 'pg';
|
||||
import {
|
||||
runFarmaciaWebhookImport,
|
||||
DEFAULT_FARMACIAS_WEBHOOK,
|
||||
@@ -24,19 +27,43 @@ import {
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const dbPath = path.join(__dirname, 'database.sqlite');
|
||||
const db = new sqlite3.Database(dbPath);
|
||||
const PG_URL = process.env.PG_URL;
|
||||
let pool = null;
|
||||
let db = null;
|
||||
|
||||
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 });
|
||||
let dbRun, dbGet;
|
||||
|
||||
if (PG_URL) {
|
||||
pool = new pg.Pool({ connectionString: PG_URL });
|
||||
|
||||
function toPositional(sql) {
|
||||
let i = 0;
|
||||
return sql.replace(/\?/g, () => `$${++i}`);
|
||||
}
|
||||
|
||||
dbGet = async (sql, params = []) => {
|
||||
const res = await pool.query(toPositional(sql), params);
|
||||
return res.rows[0] ?? null;
|
||||
};
|
||||
|
||||
dbRun = async (sql, params = []) => {
|
||||
const res = await pool.query(toPositional(sql), params);
|
||||
return { lastID: res.rows[0]?.id, changes: res.rowCount };
|
||||
};
|
||||
} else {
|
||||
const dbPath = path.join(__dirname, 'database.sqlite');
|
||||
db = new sqlite3.Database(dbPath);
|
||||
|
||||
dbRun = (sql, params = []) =>
|
||||
new Promise((resolve, reject) => {
|
||||
db.run(sql, params, function (err) {
|
||||
if (err) reject(err);
|
||||
else resolve({ lastID: this.lastID, changes: this.changes });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const dbGet = promisify(db.get.bind(db));
|
||||
dbGet = promisify(db.get.bind(db));
|
||||
}
|
||||
|
||||
function parseCli(argv) {
|
||||
const region = {};
|
||||
@@ -76,6 +103,7 @@ async function main() {
|
||||
const { url, region } = parseCli(process.argv);
|
||||
console.log('Fetching pharmacies from:', url);
|
||||
if (region) console.log('Region query:', region);
|
||||
console.log('Using:', PG_URL ? 'PostgreSQL' : 'SQLite');
|
||||
|
||||
try {
|
||||
const result = await runFarmaciaWebhookImport(dbGet, dbRun, url, region);
|
||||
@@ -97,7 +125,8 @@ async function main() {
|
||||
}
|
||||
process.exitCode = 1;
|
||||
} finally {
|
||||
db.close();
|
||||
if (pool) await pool.end();
|
||||
else if (db) db.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+149
-78
@@ -175,42 +175,68 @@ function serializeOpeningHours(value) {
|
||||
// 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,
|
||||
opening_hours TEXT
|
||||
)
|
||||
`);
|
||||
|
||||
// Add opening_hours to pre-existing pharmacies tables (no-op if already present)
|
||||
try {
|
||||
await dbRun(`ALTER TABLE pharmacies ADD COLUMN opening_hours TEXT`);
|
||||
} catch (e) {
|
||||
if (!/duplicate column/i.test(e.message)) throw e;
|
||||
// Create pharmacies table — PG when available, SQLite fallback
|
||||
if (pgPool) {
|
||||
await pgPool.query(`
|
||||
CREATE TABLE IF NOT EXISTS pharmacies (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
address TEXT NOT NULL,
|
||||
phone TEXT,
|
||||
latitude DOUBLE PRECISION,
|
||||
longitude DOUBLE PRECISION,
|
||||
opening_hours TEXT
|
||||
)
|
||||
`);
|
||||
} else {
|
||||
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,
|
||||
opening_hours TEXT
|
||||
)
|
||||
`);
|
||||
// Add opening_hours to pre-existing pharmacies tables (no-op if already present)
|
||||
try {
|
||||
await dbRun(`ALTER TABLE pharmacies ADD COLUMN opening_hours TEXT`);
|
||||
} catch (e) {
|
||||
if (!/duplicate column/i.test(e.message)) throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
)
|
||||
`);
|
||||
|
||||
await dbRun(`CREATE INDEX IF NOT EXISTS idx_pharmacy_medicine ON pharmacy_medicines(medicine_nregistro)`);
|
||||
if (pgPool) {
|
||||
await pgPool.query(`
|
||||
CREATE TABLE IF NOT EXISTS pharmacy_medicines (
|
||||
id SERIAL PRIMARY KEY,
|
||||
pharmacy_id INTEGER NOT NULL REFERENCES pharmacies(id),
|
||||
medicine_nregistro TEXT NOT NULL,
|
||||
medicine_name TEXT,
|
||||
price REAL,
|
||||
stock INTEGER DEFAULT 0,
|
||||
UNIQUE(pharmacy_id, medicine_nregistro)
|
||||
)
|
||||
`);
|
||||
await pgPool.query(`CREATE INDEX IF NOT EXISTS idx_pharmacy_medicine ON pharmacy_medicines(medicine_nregistro)`);
|
||||
} else {
|
||||
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)
|
||||
)
|
||||
`);
|
||||
await dbRun(`CREATE INDEX IF NOT EXISTS idx_pharmacy_medicine ON pharmacy_medicines(medicine_nregistro)`);
|
||||
}
|
||||
|
||||
// Create users table — PG when available, SQLite fallback
|
||||
if (pgPool) {
|
||||
@@ -395,6 +421,36 @@ if (!pgPool) {
|
||||
`);
|
||||
}
|
||||
|
||||
// Recent searches table
|
||||
if (pgPool) {
|
||||
await pgPool.query(`
|
||||
CREATE TABLE IF NOT EXISTS recent_searches (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
||||
medicine_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
active_ingredient TEXT,
|
||||
dosage TEXT,
|
||||
form TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)
|
||||
`);
|
||||
} else {
|
||||
await dbRun(`
|
||||
CREATE TABLE IF NOT EXISTS recent_searches (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
medicine_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
active_ingredient TEXT,
|
||||
dosage TEXT,
|
||||
form TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
)
|
||||
`);
|
||||
}
|
||||
|
||||
// User addresses table
|
||||
if (pgPool) {
|
||||
await pgPool.query(`
|
||||
@@ -456,7 +512,7 @@ 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(`
|
||||
const pharmacies = await userDbAll(`
|
||||
SELECT
|
||||
p.id,
|
||||
p.name,
|
||||
@@ -519,46 +575,61 @@ const requireAdmin = (req, res, next) => {
|
||||
return res.status(401).json({ error: 'Authentication required' });
|
||||
};
|
||||
|
||||
// ========== RECENT SEARCHES (session-based) ==========
|
||||
// ========== RECENT SEARCHES (database-based) ==========
|
||||
|
||||
const MAX_RECENT = 10;
|
||||
const MAX_RECENT = 5;
|
||||
|
||||
// Get recent searches for the logged-in user
|
||||
app.get('/api/search/recent', requireAuth, (req, res) => {
|
||||
res.json(req.session.recentSearches || []);
|
||||
app.get('/api/search/recent', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const userId = req.session.userId;
|
||||
const rows = await userDbAll(
|
||||
'SELECT id, medicine_id, name, active_ingredient, dosage, form, created_at FROM recent_searches WHERE user_id = ? ORDER BY created_at DESC LIMIT ?',
|
||||
[userId, MAX_RECENT]
|
||||
);
|
||||
res.json(rows.map(r => ({
|
||||
id: r.medicine_id,
|
||||
name: r.name,
|
||||
active_ingredient: r.active_ingredient,
|
||||
dosage: r.dosage,
|
||||
form: r.form,
|
||||
timestamp: r.created_at,
|
||||
})));
|
||||
} catch (error) {
|
||||
console.error('Error fetching recent searches:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Save a medicine to recent searches
|
||||
app.post('/api/search/recent', requireAuth, (req, res) => {
|
||||
app.post('/api/search/recent', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { medicine } = req.body;
|
||||
if (!medicine || !medicine.id) {
|
||||
return res.status(400).json({ error: 'Medicine data required' });
|
||||
}
|
||||
|
||||
if (!req.session.recentSearches) {
|
||||
req.session.recentSearches = [];
|
||||
}
|
||||
const userId = req.session.userId;
|
||||
|
||||
// Remove duplicate if exists (by id)
|
||||
req.session.recentSearches = req.session.recentSearches.filter(
|
||||
(m) => m.id !== medicine.id
|
||||
// Remove duplicate if exists (by medicine_id)
|
||||
await userDbRun(
|
||||
'DELETE FROM recent_searches WHERE user_id = ? AND medicine_id = ?',
|
||||
[userId, medicine.id]
|
||||
);
|
||||
|
||||
// Add to front with timestamp
|
||||
req.session.recentSearches.unshift({
|
||||
id: medicine.id,
|
||||
name: medicine.name,
|
||||
active_ingredient: medicine.active_ingredient,
|
||||
dosage: medicine.dosage,
|
||||
form: medicine.form,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
// Insert new entry
|
||||
await userDbRun(
|
||||
'INSERT INTO recent_searches (user_id, medicine_id, name, active_ingredient, dosage, form) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
[userId, medicine.id, medicine.name, medicine.active_ingredient || null, medicine.dosage || null, medicine.form || null]
|
||||
);
|
||||
|
||||
// Keep only last MAX_RECENT
|
||||
if (req.session.recentSearches.length > MAX_RECENT) {
|
||||
req.session.recentSearches = req.session.recentSearches.slice(0, MAX_RECENT);
|
||||
}
|
||||
// Trim to MAX_RECENT — delete oldest beyond limit
|
||||
await userDbRun(
|
||||
`DELETE FROM recent_searches WHERE user_id = ? AND id NOT IN (
|
||||
SELECT id FROM recent_searches WHERE user_id = ? ORDER BY created_at DESC LIMIT ?
|
||||
)`,
|
||||
[userId, userId, MAX_RECENT]
|
||||
);
|
||||
|
||||
res.json({ ok: true });
|
||||
} catch (error) {
|
||||
@@ -570,7 +641,7 @@ app.post('/api/search/recent', requireAuth, (req, res) => {
|
||||
// Get all pharmacies (for admin/debugging)
|
||||
app.get('/api/pharmacies', async (req, res) => {
|
||||
try {
|
||||
const pharmacies = await dbAll(`
|
||||
const pharmacies = await userDbAll(`
|
||||
SELECT * FROM pharmacies ORDER BY name
|
||||
`);
|
||||
res.json(pharmacies);
|
||||
@@ -1447,7 +1518,7 @@ app.post('/api/admin/pharmacies', requireAdmin, async (req, res) => {
|
||||
}
|
||||
|
||||
// Check for duplicate (same name and address)
|
||||
const existing = await dbGet(
|
||||
const existing = await userDbGet(
|
||||
'SELECT * FROM pharmacies WHERE name = ? AND address = ?',
|
||||
[name.trim(), address.trim()]
|
||||
);
|
||||
@@ -1458,7 +1529,7 @@ app.post('/api/admin/pharmacies', requireAdmin, async (req, res) => {
|
||||
|
||||
const openingHoursValue = serializeOpeningHours(opening_hours);
|
||||
|
||||
const result = await dbRun(
|
||||
const result = await userDbRun(
|
||||
'INSERT INTO pharmacies (name, address, phone, latitude, longitude, opening_hours) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
[name.trim(), address.trim(), phone ? phone.trim() : null, latitude || null, longitude || null, openingHoursValue]
|
||||
);
|
||||
@@ -1467,7 +1538,7 @@ app.post('/api/admin/pharmacies', requireAdmin, async (req, res) => {
|
||||
throw new Error('Failed to get lastID from database insert');
|
||||
}
|
||||
|
||||
const newPharmacy = await dbGet(
|
||||
const newPharmacy = await userDbGet(
|
||||
'SELECT * FROM pharmacies WHERE id = ?',
|
||||
[result.lastID]
|
||||
);
|
||||
@@ -1499,12 +1570,12 @@ app.put('/api/admin/pharmacies/:id', requireAdmin, async (req, res) => {
|
||||
|
||||
const openingHoursValue = serializeOpeningHours(opening_hours);
|
||||
|
||||
await dbRun(
|
||||
await userDbRun(
|
||||
'UPDATE pharmacies SET name = ?, address = ?, phone = ?, latitude = ?, longitude = ?, opening_hours = ? WHERE id = ?',
|
||||
[name, address, phone || null, latitude || null, longitude || null, openingHoursValue, pharmacyId]
|
||||
);
|
||||
|
||||
const updatedPharmacy = await dbGet(
|
||||
const updatedPharmacy = await userDbGet(
|
||||
'SELECT * FROM pharmacies WHERE id = ?',
|
||||
[pharmacyId]
|
||||
);
|
||||
@@ -1526,10 +1597,10 @@ app.delete('/api/admin/pharmacies/:id', requireAdmin, async (req, res) => {
|
||||
const pharmacyId = parseInt(req.params.id);
|
||||
|
||||
// Delete related pharmacy_medicines first
|
||||
await dbRun('DELETE FROM pharmacy_medicines WHERE pharmacy_id = ?', [pharmacyId]);
|
||||
await userDbRun('DELETE FROM pharmacy_medicines WHERE pharmacy_id = ?', [pharmacyId]);
|
||||
|
||||
// Delete the pharmacy
|
||||
await dbRun('DELETE FROM pharmacies WHERE id = ?', [pharmacyId]);
|
||||
await userDbRun('DELETE FROM pharmacies WHERE id = ?', [pharmacyId]);
|
||||
|
||||
res.json({ message: 'Pharmacy deleted successfully' });
|
||||
} catch (error) {
|
||||
@@ -1555,8 +1626,8 @@ app.post('/api/admin/pharmacies/import-webhook', requireAdmin, async (req, res)
|
||||
const hasRegion =
|
||||
region.lat != null || region.lon != null || region.radio != null;
|
||||
const result = await runFarmaciaWebhookImport(
|
||||
dbGet,
|
||||
dbRun,
|
||||
userDbGet,
|
||||
userDbRun,
|
||||
url.trim(),
|
||||
hasRegion ? region : null
|
||||
);
|
||||
@@ -1604,7 +1675,7 @@ app.post('/api/admin/pharmacies/import-external', requireAdmin, async (req, res)
|
||||
});
|
||||
}
|
||||
|
||||
const stats = await importPharmaciesFromRows(dbGet, dbRun, rows);
|
||||
const stats = await importPharmaciesFromRows(userDbGet, userDbRun, rows);
|
||||
res.json({ ...stats, totalReceived: rows.length, source });
|
||||
} catch (error) {
|
||||
console.error('External pharmacy import:', error);
|
||||
@@ -1636,7 +1707,7 @@ app.get('/api/admin/pharmacies/:pharmacyId/medicines', requireAdmin, async (req,
|
||||
try {
|
||||
const pharmacyId = parseInt(req.params.pharmacyId);
|
||||
|
||||
const medicines = await dbAll(`
|
||||
const medicines = await userDbAll(`
|
||||
SELECT
|
||||
pm.id,
|
||||
pm.pharmacy_id,
|
||||
@@ -1666,33 +1737,33 @@ app.post('/api/admin/pharmacy-medicines', requireAdmin, async (req, res) => {
|
||||
}
|
||||
|
||||
// Check if relationship already exists
|
||||
const existing = await dbGet(
|
||||
const existing = await userDbGet(
|
||||
'SELECT * FROM pharmacy_medicines WHERE pharmacy_id = ? AND medicine_nregistro = ?',
|
||||
[pharmacy_id, medicine_nregistro]
|
||||
);
|
||||
|
||||
if (existing) {
|
||||
// Update existing relationship
|
||||
await dbRun(
|
||||
await userDbRun(
|
||||
'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(
|
||||
await userDbRun(
|
||||
'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(
|
||||
const relationship = await userDbGet(
|
||||
`SELECT * FROM pharmacy_medicines
|
||||
WHERE pharmacy_id = ? AND medicine_nregistro = ?`,
|
||||
[pharmacy_id, medicine_nregistro]
|
||||
);
|
||||
|
||||
if (!existing) {
|
||||
const pharmacy = await dbGet('SELECT id, name FROM pharmacies WHERE id = ?', [pharmacy_id]);
|
||||
const pharmacy = await userDbGet('SELECT id, name FROM pharmacies WHERE id = ?', [pharmacy_id]);
|
||||
sendPushForMedicine({
|
||||
medicine_nregistro,
|
||||
medicine_name: medicine_name || relationship?.medicine_name || null,
|
||||
@@ -1713,12 +1784,12 @@ app.put('/api/admin/pharmacy-medicines/:id', requireAdmin, async (req, res) => {
|
||||
const id = parseInt(req.params.id);
|
||||
const { price, stock } = req.body;
|
||||
|
||||
await dbRun(
|
||||
await userDbRun(
|
||||
'UPDATE pharmacy_medicines SET price = ?, stock = ? WHERE id = ?',
|
||||
[price || null, stock || 0, id]
|
||||
);
|
||||
|
||||
const updated = await dbGet(
|
||||
const updated = await userDbGet(
|
||||
'SELECT * FROM pharmacy_medicines WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
@@ -1738,7 +1809,7 @@ app.put('/api/admin/pharmacy-medicines/:id', requireAdmin, async (req, res) => {
|
||||
app.delete('/api/admin/pharmacy-medicines/:id', requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const id = parseInt(req.params.id);
|
||||
await dbRun('DELETE FROM pharmacy_medicines WHERE id = ?', [id]);
|
||||
await userDbRun('DELETE FROM pharmacy_medicines WHERE id = ?', [id]);
|
||||
res.json({ message: 'Medicine removed from pharmacy successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error deleting pharmacy-medicine:', error);
|
||||
|
||||
Reference in New Issue
Block a user