076ca2d590
Run Tests on Branches / Frontend Mobile Tests (push) Has been skipped
Run Tests on Branches / Parapharmacy API Tests (push) Successful in 2m2s
Run Tests on Branches / Detect Changes (push) Successful in 14s
Run Tests on Branches / Backend Tests (push) Successful in 1m58s
Run Tests on Branches / Frontend Tests (push) Has been skipped
Run Tests on Branches / PIP Platform Tests (push) Has been skipped
546 lines
15 KiB
Markdown
546 lines
15 KiB
Markdown
# FarmaFinder
|
|
|
|
A web application to search for medicines from the official Spanish CIMA database and find which pharmacies sell them.
|
|
|
|
## Features
|
|
|
|
### Web App (Desktop/PWA)
|
|
- Real-time medicine search from CIMA API (Agencia Espanola de Medicamentos)
|
|
- Redis caching for improved performance
|
|
- View pharmacies that sell a specific medicine
|
|
- See prices and stock availability
|
|
- Responsive design for mobile and desktop
|
|
- Admin Panel - Manage pharmacies and link medicines
|
|
- Secure authentication - Login required to access admin features
|
|
- Add, edit, and delete pharmacies
|
|
- Search medicines from CIMA database
|
|
- Link medicines to pharmacies with prices and stock
|
|
|
|
### Mobile App (React Native)
|
|
- Native iOS/Android experience with Expo
|
|
- Medicine search with real-time results
|
|
- Interactive map with pharmacy markers
|
|
- Barcode scanner for quick medicine lookup
|
|
- Push notifications for availability alerts
|
|
- Biometric authentication (Face ID / Touch ID)
|
|
- Offline cache for favorite medicines
|
|
|
|
## Tech Stack
|
|
|
|
| App | Stack |
|
|
|-----|-------|
|
|
| Backend | Node.js + Express, SQLite/PostgreSQL, Redis |
|
|
| Parapharmacy API | Node.js + Express, MongoDB |
|
|
| Frontend (Web) | React + Vite, Capacitor |
|
|
| Frontend (Mobile) | Expo SDK 57 + React Native, Zustand, Axios + TanStack Query |
|
|
| Workflow Automation | N8N |
|
|
| Build system | Turborepo |
|
|
| Package manager | npm workspaces |
|
|
|
|
## Prerequisites
|
|
|
|
- Node.js v20+
|
|
- npm v9+
|
|
- Redis server v6.0+ (or use Docker)
|
|
- Docker + Docker Compose v2 (optional, for containerized deployment)
|
|
|
|
## Project Structure
|
|
|
|
This is a **Turborepo monorepo**. All applications live under `apps/`:
|
|
|
|
```
|
|
FarmaFinder/
|
|
├── package.json # Root: workspaces + turbo scripts
|
|
├── turbo.json # Turborepo task configuration
|
|
├── docker-compose.yml # Full stack: backend + frontend + Redis + Postgres + MongoDB + N8N
|
|
├── .env.example # Environment variables template
|
|
│
|
|
├── apps/
|
|
│ ├── backend/ # Node.js + Express API (medicines)
|
|
│ │ ├── Dockerfile
|
|
│ │ ├── server.js # Express server and API routes
|
|
│ │ ├── cima-service.js # CIMA API integration with Redis cache
|
|
│ │ ├── redis-client.js # Redis connection configuration
|
|
│ │ ├── seed.js # Database seeding script
|
|
│ │ ├── create-admin.js # Admin user creation script
|
|
│ │ └── package.json
|
|
│ │
|
|
│ ├── parapharmacy-api/ # Parapharmacy products API
|
|
│ │ ├── Dockerfile
|
|
│ │ ├── src/
|
|
│ │ │ ├── server.js # Express + Swagger
|
|
│ │ │ ├── config.js # Configuration
|
|
│ │ │ ├── models/
|
|
│ │ │ │ └── Product.js
|
|
│ │ │ └── routes/
|
|
│ │ │ └── products.js
|
|
│ │ ├── README.md
|
|
│ │ └── package.json
|
|
│ │
|
|
│ ├── frontend/ # React + Vite (Desktop/PWA)
|
|
│ │ ├── Dockerfile
|
|
│ │ ├── nginx.conf # Nginx config for Docker
|
|
│ │ ├── src/
|
|
│ │ │ ├── components/ # React components
|
|
│ │ │ ├── views/ # View components (Public/Admin)
|
|
│ │ │ ├── App.jsx
|
|
│ │ │ └── main.jsx
|
|
│ │ └── package.json
|
|
│ │
|
|
│ ├── frontend-mobile/ # Expo + React Native (iOS/Android)
|
|
│ │ ├── app/ # Expo Router screens
|
|
│ │ ├── components/
|
|
│ │ ├── services/
|
|
│ │ ├── store/
|
|
│ │ └── package.json
|
|
│ │
|
|
│ ├── scraper/ # Puppeteer scraper (legacy)
|
|
│ │ └── package.json
|
|
│ │
|
|
│ └── pip-platform/ # Python FastAPI platform (separate docker-compose)
|
|
│ ├── Dockerfile
|
|
│ ├── docker-compose.yml
|
|
│ └── pyproject.toml
|
|
│
|
|
├── n8n/ # N8N workflow automation
|
|
│ ├── workflows/ # Workflow JSON files
|
|
│ │ ├── parapharmacy-scraper.json
|
|
│ │ └── parapharmacy-manual-scraper.json
|
|
│ └── README.md
|
|
│
|
|
├── API/ # Shared API source files
|
|
├── scripts/ # Build/utility scripts
|
|
└── docs/ # Documentation
|
|
└── parapharmacy.md # Parapharmacy system documentation
|
|
```
|
|
|
|
## Features
|
|
|
|
### Parapharmacy Search
|
|
- Scraping de múltiples tiendas españolas (Promofarma, Pharmarket, DocMorris, etc.)
|
|
- API REST dedicada con MongoDB
|
|
- Scraping automático cada 3 días via N8N
|
|
- Búsqueda full-text por nombre, marca y categoría
|
|
- [Documentación completa](docs/parapharmacy.md)
|
|
|
|
---
|
|
|
|
## Quick Start
|
|
|
|
### Install dependencies
|
|
|
|
```bash
|
|
npm install
|
|
```
|
|
|
|
This installs all workspace dependencies (backend, frontend, mobile, scraper) via npm workspaces.
|
|
|
|
### Development
|
|
|
|
```bash
|
|
# Start everything (backend + frontend)
|
|
npm run dev
|
|
|
|
# Start only backend
|
|
npm run dev:backend
|
|
|
|
# Start only frontend
|
|
npm run dev:frontend
|
|
|
|
# Start mobile app
|
|
npx turbo run dev --filter=frontend-mobile
|
|
```
|
|
|
|
### Build
|
|
|
|
```bash
|
|
# Build all apps
|
|
npm run build
|
|
|
|
# Build only frontend
|
|
npm run build:web
|
|
```
|
|
|
|
### Test
|
|
|
|
```bash
|
|
# Run all tests
|
|
npm test
|
|
|
|
# Test specific app
|
|
npm test --workspace=farma-clic-backend
|
|
npm test --workspace=farma-clic-frontend
|
|
```
|
|
|
|
## Docker Setup
|
|
|
|
Runs the full stack with a single command.
|
|
|
|
```bash
|
|
# Copy and configure environment
|
|
cp .env.example .env
|
|
# Edit .env with your settings (especially passwords)
|
|
|
|
docker compose up --build
|
|
```
|
|
|
|
### Production Environment Variables (IMPORTANT)
|
|
|
|
The root `.env` file is the **source of truth** for all Docker Compose services. The `docker-compose.yml` uses `${VAR:?...}` syntax which reads from this file. **Do not use placeholder values in production** — the backend validates them on startup and will crash.
|
|
|
|
Required variables to set with real secrets:
|
|
|
|
```env
|
|
# PostgreSQL password (used by postgres, backend, n8n, exporters)
|
|
PG_PASSWORD=<strong-random-hex>
|
|
|
|
# Backend session secret (required, non-placeholder)
|
|
SESSION_SECRET=<strong-random-hex>
|
|
|
|
# Backend CORS origin (must be your real domain, not localhost)
|
|
CORS_ORIGIN=https://farmacias.hacecalor.net
|
|
|
|
# N8N admin password
|
|
N8N_PASSWORD=<strong-random-string>
|
|
|
|
# Parapharmacy API keys (required for product ingestion)
|
|
INGEST_API_KEY=<strong-random-hex>
|
|
ADMIN_API_KEY=<strong-random-hex>
|
|
```
|
|
|
|
Generate secrets with:
|
|
```bash
|
|
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
|
```
|
|
|
|
**After changing `PG_PASSWORD`**, you must reset the PostgreSQL volume:
|
|
```bash
|
|
docker compose down
|
|
docker volume rm farmafinder_postgres_data
|
|
docker compose up -d
|
|
```
|
|
|
|
Then re-seed data (see "First Run" below).
|
|
|
|
### Services
|
|
|
|
| Service | URL | Description |
|
|
|---------|-----|-------------|
|
|
| Frontend | http://localhost:4000 | React web app |
|
|
| Backend API | http://localhost:3001 | Medicines API |
|
|
| Parapharmacy API | http://localhost:3002 | Parapharmacy products API |
|
|
| Swagger Docs | http://localhost:3002/api/docs | API documentation |
|
|
| N8N | http://localhost:5678 | Workflow automation |
|
|
|
|
### First Run
|
|
|
|
```bash
|
|
# Create admin user for FarmaFinder
|
|
docker compose exec backend node create-admin.js
|
|
# Default: admin / admin123
|
|
|
|
# Seed sample pharmacies (SQLite — for local dev)
|
|
docker compose exec backend node seed.js
|
|
|
|
# Seed parapharmacy products (requires INGEST_API_KEY)
|
|
# The n8n-init container handles this automatically on first run.
|
|
# To re-seed manually after a PostgreSQL reset:
|
|
docker compose exec parapharmacy-api node -e "
|
|
const http = require('http');
|
|
const fs = require('fs');
|
|
const seed = JSON.parse(fs.readFileSync('/home/node/seed.json','utf8'));
|
|
const body = JSON.stringify(seed);
|
|
const req = http.request('http://localhost:3002/api/products/bulk', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'x-service-key': process.env.INGEST_API_KEY, 'Content-Length': Buffer.byteLength(body) }
|
|
}, res => { let d=''; res.on('data',c=>d+=c); res.on('end',()=>console.log(d)); });
|
|
req.write(body); req.end();
|
|
"
|
|
```
|
|
|
|
After a PostgreSQL volume reset, the n8n-init container will re-import workflows automatically. The backend re-creates its PG tables on startup (`initDatabase()` in `server.js`).
|
|
|
|
### N8N Setup
|
|
|
|
N8N auto-creates an admin account on first start:
|
|
- **Email**: admin@farmafinder.com
|
|
- **Password**: change-me (configurable in `.env`)
|
|
|
|
See [Parapharmacy Documentation](docs/parapharmacy.md) for details.
|
|
|
|
### Stop
|
|
|
|
```bash
|
|
docker compose down
|
|
```
|
|
|
|
### Reset Data
|
|
|
|
```bash
|
|
docker compose down -v
|
|
```
|
|
|
|
### Reset PostgreSQL Only (keep other data)
|
|
|
|
```bash
|
|
docker compose down
|
|
docker volume rm farmafinder_postgres_data
|
|
docker compose up -d
|
|
# n8n-init re-imports workflows; backend re-creates tables on startup
|
|
# Re-seed parapharmacy products (see First Run above)
|
|
```
|
|
|
|
## Manual Setup
|
|
|
|
### 1. Install Redis
|
|
|
|
**Ubuntu/Debian:**
|
|
```bash
|
|
sudo apt-get install redis-server
|
|
sudo systemctl start redis-server
|
|
```
|
|
|
|
**macOS:**
|
|
```bash
|
|
brew install redis
|
|
brew services start redis
|
|
```
|
|
|
|
**Docker:**
|
|
```bash
|
|
docker run -d -p 6379:6379 redis:alpine
|
|
```
|
|
|
|
Verify: `redis-cli ping` should respond `PONG`.
|
|
|
|
### 2. Configure Environment (Optional)
|
|
|
|
Create `apps/backend/.env`:
|
|
|
|
```env
|
|
REDIS_HOST=localhost
|
|
REDIS_PORT=6379
|
|
REDIS_PASSWORD=
|
|
SESSION_SECRET=your-secret-key-here
|
|
```
|
|
|
|
### 3. Initialize Database
|
|
|
|
```bash
|
|
# Seed sample pharmacies
|
|
npm run dev --workspace=farma-clic-backend -- run seed
|
|
|
|
# Create admin user (default: admin / admin123)
|
|
npm run dev --workspace=farma-clic-backend -- run create-admin
|
|
```
|
|
|
|
### 4. Run
|
|
|
|
```bash
|
|
npm run dev
|
|
```
|
|
|
|
## API Endpoints
|
|
|
|
### Medicines (Backend - Port 3001)
|
|
|
|
**Public:**
|
|
- `GET /api/medicines/search?q=<query>` - Search medicines (CIMA API)
|
|
- `GET /api/medicines/:nregistro` - Medicine details
|
|
- `GET /api/medicines/:nregistro/pharmacies` - Pharmacies selling a medicine
|
|
- `GET /api/pharmacies` - All pharmacies
|
|
|
|
**Parapharmacy Proxy:**
|
|
- `GET /api/products/parapharmacy/search?q=<query>` - Search parapharmacy products
|
|
- `GET /api/products/parapharmacy/:id` - Parapharmacy product details
|
|
- `GET /api/products/parapharmacy/categories` - List categories
|
|
- `GET /api/products/parapharmacy/brands` - List brands
|
|
|
|
**Auth:**
|
|
- `POST /api/auth/login` - Login
|
|
- `POST /api/auth/logout` - Logout
|
|
- `GET /api/auth/check` - Check auth status
|
|
|
|
**Admin:**
|
|
- `POST /api/admin/pharmacies` - Add pharmacy
|
|
- `PUT /api/admin/pharmacies/:id` - Update pharmacy
|
|
- `DELETE /api/admin/pharmacies/:id` - Delete pharmacy
|
|
- `GET /api/admin/medicines?q=<query>` - Search medicines
|
|
- `GET /api/admin/pharmacies/:id/medicines` - Linked medicines
|
|
- `POST /api/admin/pharmacy-medicines` - Link medicine to pharmacy
|
|
- `PUT /api/admin/pharmacy-medicines/:id` - Update price/stock
|
|
- `DELETE /api/admin/pharmacy-medicines/:id` - Remove link
|
|
|
|
### Parapharmacy API (Port 3002)
|
|
|
|
- `GET /api/products/search?q=<query>` - Search products (full-text)
|
|
- `GET /api/products/:id` - Product details
|
|
- `GET /api/products` - List products
|
|
- `POST /api/products` - Create product
|
|
- `POST /api/products/bulk` - Bulk upsert (for scrapers)
|
|
- `PUT /api/products/:id` - Update product
|
|
- `DELETE /api/products/:id` - Delete product
|
|
- `GET /api/products/categories` - List categories
|
|
- `GET /api/products/brands` - List brands
|
|
- `GET /api/sources` - Configured sources
|
|
- `GET /api/health` - Health check
|
|
- `GET /api/docs` - Swagger documentation
|
|
|
|
See [Parapharmacy Documentation](docs/parapharmacy.md) for details.
|
|
|
|
## Database Schema
|
|
|
|
In production (Docker), the backend uses **PostgreSQL**. In local dev without PG, it falls back to **SQLite**.
|
|
|
|
### PostgreSQL Tables (Production)
|
|
|
|
**pharmacies**: `id`, `name`, `address`, `phone`, `latitude`, `longitude`, `opening_hours`
|
|
|
|
**pharmacy_medicines**: `id`, `pharmacy_id`, `medicine_nregistro`, `medicine_name`, `price`, `stock`
|
|
|
|
**users**: `id`, `username`, `password_hash`, `is_admin`, `address`, `latitude`, `longitude`, `created_at`
|
|
|
|
**user_alerts**: `id`, `user_id`, `type`, `medicine_nregistro`, `title`, `detail`, `schedule`, `created_at`, `updated_at`
|
|
|
|
**push_subscriptions**: `id`, `user_id`, `medicine_nregistro`, `medicine_name`, `endpoint`, `p256dh`, `auth`, `created_at`
|
|
|
|
**push_subscriptions_pharmacy**: `id`, `user_id`, `medicine_nregistro`, `medicine_name`, `pharmacy_id`, `endpoint`, `p256dh`, `auth`, `created_at`
|
|
|
|
**expo_push_tokens**: `id`, `user_id`, `expo_token`, `created_at`
|
|
|
|
### SQLite Tables (Local Dev Fallback)
|
|
|
|
Same schema as above minus foreign key constraints and PostgreSQL-specific types.
|
|
|
|
### Redis Cache
|
|
|
|
- `medicines:search:{query}` - Search results (TTL: 1h)
|
|
- `medicine:{nregistro}` - Medicine details (TTL: 24h)
|
|
|
|
## Mobile App Setup
|
|
|
|
### Prerequisites
|
|
|
|
- Node.js v20+
|
|
- Expo CLI: `npm install -g expo-cli`
|
|
- EAS CLI: `npm install -g eas-cli`
|
|
- iOS: Xcode + CocoaPods (Mac only)
|
|
- Android: Android Studio + SDK
|
|
|
|
### Development
|
|
|
|
```bash
|
|
# Install dependencies (already done via npm install at root)
|
|
|
|
# Start mobile dev server
|
|
npx turbo run dev --filter=frontend-mobile
|
|
|
|
# Scan QR code with Expo Go app
|
|
```
|
|
|
|
### Native Features
|
|
|
|
| Feature | Implementation |
|
|
|---------|---------------|
|
|
| Barcode Scanner | `expo-camera` with `CameraView` |
|
|
| Push Notifications | `expo-notifications` |
|
|
| Biometrics | `expo-local-authentication` |
|
|
| Maps | `react-native-maps` |
|
|
| Secure Storage | `expo-secure-store` |
|
|
|
|
### EAS Build
|
|
|
|
```bash
|
|
cd apps/frontend-mobile
|
|
|
|
# Development build
|
|
eas build --profile development --platform ios
|
|
eas build --profile development --platform android
|
|
|
|
# Production build
|
|
eas build --profile production --platform android
|
|
eas build --profile production --platform ios
|
|
|
|
# Submit to stores
|
|
eas submit --profile production --platform android
|
|
eas submit --profile production --platform ios
|
|
```
|
|
|
|
### Environment Configuration
|
|
|
|
```typescript
|
|
// apps/frontend-mobile/constants/config.ts
|
|
const ENV = {
|
|
development: { API_BASE_URL: 'http://localhost:3001/api' },
|
|
production: { API_BASE_URL: 'https://your-production-api.com/api' },
|
|
};
|
|
```
|
|
|
|
## N8N Workflow Automation
|
|
|
|
N8N handles automated scraping of parapharmacy products.
|
|
|
|
### Access
|
|
- **URL**: http://localhost:5678
|
|
- **Email**: admin@farmafinder.com
|
|
- **Password**: change-me (change in `.env`)
|
|
|
|
### Workflows
|
|
|
|
| Workflow | Trigger | Description |
|
|
|----------|---------|-------------|
|
|
| Parapharmacy Scraper | Every 3 days at 2am | Automatic scraping (inactive by default) |
|
|
| Parapharmacy Manual Scraper | POST `/webhook/scrape-parapharmacy` | On-demand scraping |
|
|
|
|
### Manual Scraping
|
|
|
|
```bash
|
|
curl -X POST http://localhost:5678/webhook/scrape-parapharmacy
|
|
```
|
|
|
|
See [N8N Documentation](n8n/README.md) for details.
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Redis Connection Issues
|
|
```bash
|
|
redis-cli ping # Should respond: PONG
|
|
redis-server # Start if not running
|
|
redis-cli FLUSHALL # Clear cache
|
|
```
|
|
|
|
### CIMA API Timeout
|
|
- Check internet connection
|
|
- CIMA API may be temporarily unavailable
|
|
- App falls back to cached data
|
|
|
|
### Database Reset
|
|
```bash
|
|
cd apps/backend
|
|
rm database.sqlite
|
|
npm run seed
|
|
npm run create-admin
|
|
```
|
|
|
|
### Turborepo Cache Issues
|
|
```bash
|
|
npx turbo clean # Clear Turbo cache
|
|
rm -rf node_modules # Full reset
|
|
npm install
|
|
```
|
|
|
|
## External Resources
|
|
|
|
- [CIMA API](https://cima.aemps.es/)
|
|
- [Turborepo](https://turbo.build/repo)
|
|
- [Redis](https://redis.io/documentation)
|
|
- [React](https://react.dev)
|
|
- [Express](https://expressjs.com)
|
|
- [Expo](https://docs.expo.dev)
|
|
|
|
## License
|
|
|
|
ISC
|