chore: document and standardize hosts/ports across apps
This commit is contained in:
@@ -45,6 +45,9 @@ cd apps/SeleniumService
|
|||||||
python3 agent.py
|
python3 agent.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 📖 Developer Documentation
|
||||||
|
|
||||||
|
- [Development Hosts & Ports](docs/ports.md) — which app runs on which host/port
|
||||||
|
|
||||||
|
|
||||||
## This in a Turborepo. What's inside?
|
## This in a Turborepo. What's inside?
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
HOST=localhost
|
NODE_ENV="development"
|
||||||
|
HOST=0.0.0.0
|
||||||
PORT=5000
|
PORT=5000
|
||||||
FRONTEND_URL=http://localhost:3000
|
FRONTEND_URLS=http://localhost:3000,http://192.168.1.8:3000
|
||||||
JWT_SECRET = 'dentalsecret'
|
JWT_SECRET = 'dentalsecret'
|
||||||
DB_HOST=localhost
|
DB_HOST=localhost
|
||||||
DB_USER=postgres
|
DB_USER=postgres
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ import dotenv from "dotenv";
|
|||||||
import { startBackupCron } from "./cron/backupCheck";
|
import { startBackupCron } from "./cron/backupCheck";
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
const FRONTEND_URL = process.env.FRONTEND_URL;
|
const NODE_ENV = (
|
||||||
|
process.env.NODE_ENV ||
|
||||||
|
process.env.ENV ||
|
||||||
|
"development"
|
||||||
|
).toLowerCase();
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
@@ -17,9 +21,48 @@ app.use(express.json());
|
|||||||
app.use(express.urlencoded({ extended: true })); // For form data
|
app.use(express.urlencoded({ extended: true })); // For form data
|
||||||
app.use(apiLogger);
|
app.use(apiLogger);
|
||||||
|
|
||||||
|
// --- CORS handling (flexible for dev and strict for prod) ---
|
||||||
|
/**
|
||||||
|
* FRONTEND_URLS env value: comma-separated allowed origins
|
||||||
|
* Example: FRONTEND_URLS=http://localhost:3000,http://192.168.1.8:3000
|
||||||
|
*/
|
||||||
|
const rawFrontendUrls =
|
||||||
|
process.env.FRONTEND_URLS || process.env.FRONTEND_URL || "";
|
||||||
|
const FRONTEND_URLS = rawFrontendUrls
|
||||||
|
.split(",")
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
// helper to see if origin is allowed
|
||||||
|
function isOriginAllowed(origin?: string | null) {
|
||||||
|
if (!origin) return true; // allow non-browser clients (curl/postman)
|
||||||
|
|
||||||
|
if (NODE_ENV !== "production") {
|
||||||
|
// Dev mode: allow localhost origins automatically
|
||||||
|
if (
|
||||||
|
origin.startsWith("http://localhost") ||
|
||||||
|
origin.startsWith("http://127.0.0.1")
|
||||||
|
)
|
||||||
|
return true;
|
||||||
|
// allow explicit FRONTEND_URLS if provided
|
||||||
|
if (FRONTEND_URLS.includes(origin)) return true;
|
||||||
|
// optionally allow the server's LAN IP if FRONTEND_LAN_IP is provided
|
||||||
|
const lanIp = process.env.FRONTEND_LAN_IP;
|
||||||
|
if (lanIp && origin.startsWith(`http://${lanIp}`)) return true;
|
||||||
|
// fallback: deny if not matched
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// production: strict whitelist — must match configured FRONTEND_URLS exactly
|
||||||
|
return FRONTEND_URLS.includes(origin);
|
||||||
|
}
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
cors({
|
cors({
|
||||||
origin: FRONTEND_URL,
|
origin: (origin, cb) => {
|
||||||
|
if (isOriginAllowed(origin)) return cb(null, true);
|
||||||
|
cb(new Error(`CORS: Origin ${origin} not allowed`));
|
||||||
|
},
|
||||||
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
||||||
allowedHeaders: ["Content-Type", "Authorization"],
|
allowedHeaders: ["Content-Type", "Authorization"],
|
||||||
credentials: true,
|
credentials: true,
|
||||||
|
|||||||
@@ -3,11 +3,18 @@ import dotenv from "dotenv";
|
|||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const HOST = process.env.HOST;
|
const NODE_ENV = (
|
||||||
const PORT = process.env.PORT;
|
process.env.NODE_ENV ||
|
||||||
|
process.env.ENV ||
|
||||||
|
"development"
|
||||||
|
).toLowerCase();
|
||||||
|
const HOST = process.env.HOST || "0.0.0.0";
|
||||||
|
const PORT = Number(process.env.PORT) || 5000;
|
||||||
|
|
||||||
const server = app.listen(PORT, () => {
|
const server = app.listen(PORT, HOST, () => {
|
||||||
console.log(`✅ Server running at http://${HOST}:${PORT}`);
|
console.log(
|
||||||
|
`✅ Server running in ${NODE_ENV} mode at http://${HOST}:${PORT}`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle startup errors
|
// Handle startup errors
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
|
NODE_ENV=development
|
||||||
|
HOST=0.0.0.0
|
||||||
|
PORT=3000
|
||||||
VITE_API_BASE_URL_BACKEND=http://localhost:5000
|
VITE_API_BASE_URL_BACKEND=http://localhost:5000
|
||||||
|
|
||||||
@@ -1,19 +1,20 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig, loadEnv } from "vite";
|
||||||
import react from '@vitejs/plugin-react'
|
import react from "@vitejs/plugin-react";
|
||||||
import path from 'path';
|
import path from "path";
|
||||||
|
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
const env = loadEnv(mode, process.cwd(), "");
|
||||||
|
|
||||||
// https://vite.dev/config/
|
return {
|
||||||
export default defineConfig({
|
plugins: [react()],
|
||||||
plugins: [
|
|
||||||
react(),
|
|
||||||
],
|
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
host: env.HOST,
|
||||||
|
port: Number(env.PORT),
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': path.resolve(__dirname, 'src')
|
"@": path.resolve(__dirname, "src"),
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
2
apps/PatientDataExtractorService/.env.example
Normal file
2
apps/PatientDataExtractorService/.env.example
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
HOST=localhost
|
||||||
|
PORT=5001
|
||||||
@@ -1,8 +1,22 @@
|
|||||||
from flask import Flask, request, jsonify
|
from fastapi import FastAPI, UploadFile, File, HTTPException
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
import uvicorn
|
||||||
import fitz # PyMuPDF
|
import fitz # PyMuPDF
|
||||||
import re
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
app = Flask(__name__)
|
from dotenv import load_dotenv
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
# Optional: allow CORS for development
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"], # change in production
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
DOB_RE = re.compile(r'(?<!\d)(\d{1,2})/(\d{1,2})/(\d{4})(?!\d)')
|
DOB_RE = re.compile(r'(?<!\d)(\d{1,2})/(\d{1,2})/(\d{4})(?!\d)')
|
||||||
ID_RE = re.compile(r'^\d{8,14}$') # 8–14 digits, whole line
|
ID_RE = re.compile(r'^\d{8,14}$') # 8–14 digits, whole line
|
||||||
@@ -14,10 +28,18 @@ STOP_WORDS = {
|
|||||||
'provider', 'printed on', 'member id', 'name', 'date of birth'
|
'provider', 'printed on', 'member id', 'name', 'date of birth'
|
||||||
}
|
}
|
||||||
|
|
||||||
@app.route("/extract", methods=["POST"])
|
@app.post("/extract")
|
||||||
def extract():
|
async def extract(pdf: UploadFile = File(...)):
|
||||||
file = request.files['pdf']
|
if not pdf:
|
||||||
doc = fitz.open(stream=file.read(), filetype="pdf")
|
raise HTTPException(status_code=400, detail="Missing 'pdf' file")
|
||||||
|
|
||||||
|
content = await pdf.read()
|
||||||
|
try:
|
||||||
|
doc = fitz.open(stream=content, filetype="pdf")
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=400, detail=f"Unable to open PDF: {e}")
|
||||||
|
|
||||||
|
# Extract text from all pages
|
||||||
text = "\n".join(page.get_text("text") for page in doc)
|
text = "\n".join(page.get_text("text") for page in doc)
|
||||||
lines = [line.strip() for line in text.splitlines() if line.strip()]
|
lines = [line.strip() for line in text.splitlines() if line.strip()]
|
||||||
|
|
||||||
@@ -34,7 +56,7 @@ def extract():
|
|||||||
break
|
break
|
||||||
|
|
||||||
if id_idx == -1:
|
if id_idx == -1:
|
||||||
return jsonify({"memberId": "", "name": "", "dob": ""})
|
return {"memberId": "", "name": "", "dob": ""}
|
||||||
|
|
||||||
# 2) Scan forward to collect name + DOB; handle both same-line and next-line cases
|
# 2) Scan forward to collect name + DOB; handle both same-line and next-line cases
|
||||||
collected = []
|
collected = []
|
||||||
@@ -61,11 +83,13 @@ def extract():
|
|||||||
# fallback: if we didn't find a date, assume first collected line(s) are name
|
# fallback: if we didn't find a date, assume first collected line(s) are name
|
||||||
name = blob
|
name = blob
|
||||||
|
|
||||||
return jsonify({
|
return {
|
||||||
"memberId": member_id,
|
"memberId": member_id,
|
||||||
"name": name,
|
"name": name,
|
||||||
"dob": dob
|
"dob": dob
|
||||||
})
|
}
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(port=5001)
|
host = os.getenv("HOST")
|
||||||
|
port = int(os.getenv("PORT"))
|
||||||
|
uvicorn.run(app, host=host, port=port)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from typing import List, Optional
|
|||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import uvicorn
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
@@ -157,8 +157,6 @@ async def extract_csv(files: List[UploadFile] = File(...), filename: Optional[st
|
|||||||
# Entrypoint (same pattern as your selenium app)
|
# Entrypoint (same pattern as your selenium app)
|
||||||
# -------------------------------------------------
|
# -------------------------------------------------
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
|
||||||
host = os.getenv("HOST")
|
host = os.getenv("HOST")
|
||||||
port = int(os.getenv("PORT"))
|
port = int(os.getenv("PORT"))
|
||||||
reload_flag = os.getenv("RELOAD", "false").lower() == "true"
|
uvicorn.run(app, host=host, port=port)
|
||||||
uvicorn.run(app, host=host, port=port, reload=reload_flag)
|
|
||||||
|
|||||||
2
apps/SeleniumService/.env.example
Normal file
2
apps/SeleniumService/.env.example
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
HOST=localhost
|
||||||
|
PORT=5002
|
||||||
@@ -4,7 +4,10 @@ import uvicorn
|
|||||||
import asyncio
|
import asyncio
|
||||||
from selenium_claimSubmitWorker import AutomationMassHealth
|
from selenium_claimSubmitWorker import AutomationMassHealth
|
||||||
from selenium_eligibilityCheckWorker import AutomationMassHealthEligibilityCheck
|
from selenium_eligibilityCheckWorker import AutomationMassHealthEligibilityCheck
|
||||||
|
import os
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
# Allow 1 selenium session at a time
|
# Allow 1 selenium session at a time
|
||||||
@@ -89,4 +92,6 @@ async def get_status():
|
|||||||
}
|
}
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
uvicorn.run(app, host="0.0.0.0", port=5002)
|
host = os.getenv("HOST")
|
||||||
|
port = int(os.getenv("PORT"))
|
||||||
|
uvicorn.run(app, host=host, port=port)
|
||||||
|
|||||||
88
docs/ports.md
Normal file
88
docs/ports.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# 🛰️ Development Hosts & Ports
|
||||||
|
|
||||||
|
This document defines the default **host** and **port** used by each app/service
|
||||||
|
in this turborepo.
|
||||||
|
Update this file whenever a new service is added or port is changed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌐 Frontend (React + Vite)
|
||||||
|
- **Host:** `localhost` (default)
|
||||||
|
- Use `0.0.0.0` if you need LAN access (phone/other device on same Wi-Fi).
|
||||||
|
- **Port:** `3000`
|
||||||
|
- **Access URLs:**
|
||||||
|
- Local: [http://localhost:3000](http://localhost:3000)
|
||||||
|
- LAN: `http://<your-ip>:3000` (only if HOST=0.0.0.0)
|
||||||
|
|
||||||
|
|
||||||
|
**Current setup:**
|
||||||
|
Frontend is running on `0.0.0.0` and is accessible via the device IP.
|
||||||
|
|
||||||
|
**`.env` file:**
|
||||||
|
```env
|
||||||
|
NODE_ENV=development
|
||||||
|
HOST=0.0.0.0
|
||||||
|
PORT=3000
|
||||||
|
VITE_API_BASE_URL_BACKEND=http://192.168.1.8:5000
|
||||||
|
```
|
||||||
|
|
||||||
|
Based on backend HOST and PORT. Currently Backend runs on 0.0.0.0 so its accessible all over the same network.
|
||||||
|
Change the Backend url if needed,
|
||||||
|
|
||||||
|
And, VITE_API_BASE_URL_BACKEND shows the backend url of the network, make localhost if only own device to work with.
|
||||||
|
Or change accordingly with real IP.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Backend (FastAPI)
|
||||||
|
- **Host:** `localhost`
|
||||||
|
- **Port:** `5000`
|
||||||
|
- **Access URL:** [http://localhost:5000](http://localhost:5000)
|
||||||
|
|
||||||
|
|
||||||
|
**Current setup:**
|
||||||
|
Currently runs for all network, and allow given frontend urls. Change accordingly.
|
||||||
|
|
||||||
|
|
||||||
|
**`.env` file:**
|
||||||
|
```env
|
||||||
|
NODE_ENV="development"
|
||||||
|
HOST=0.0.0.0
|
||||||
|
PORT=5000
|
||||||
|
FRONTEND_URLS=http://localhost:3000,http://192.168.1.8:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧾 Patient Data Extractor Service
|
||||||
|
- **Host:** `localhost`
|
||||||
|
- **Port:** `5001`
|
||||||
|
- **Access URL:** [http://localhost:5001](http://localhost:5001)
|
||||||
|
|
||||||
|
|
||||||
|
## 💳 Selenium Service
|
||||||
|
- **Host:** `localhost`
|
||||||
|
- **Port:** `5002`
|
||||||
|
- **Access URL:** [http://localhost:5002](http://localhost:5002)
|
||||||
|
|
||||||
|
|
||||||
|
## 💳 Payment OCR Service
|
||||||
|
- **Host:** `localhost`
|
||||||
|
- **Port:** `5003`
|
||||||
|
- **Access URL:** [http://localhost:5003](http://localhost:5003)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📖 Notes
|
||||||
|
- These values come from per-app `.env` files:
|
||||||
|
- `HOST` controls binding (`localhost` = loopback only, `0.0.0.0` = all interfaces).
|
||||||
|
- `PORT` controls the service’s port.
|
||||||
|
- Frontend uses additional variables prefixed with `VITE_` for client-side access (e.g. `VITE_API_BASE_URL_BACKEND`).
|
||||||
|
- In production, ports and hosts may differ (configured by deployment platform).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
✅ **Action for developers:**
|
||||||
|
1. Copy `.env.example` → `.env` inside each app folder.
|
||||||
|
2. Adjust `HOST` / `PORT` if your ports are already taken.
|
||||||
|
3. Run `npm run dev` from the repo root.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
"db:generate": "prisma generate --schema=packages/db/prisma/schema.prisma && ts-node packages/db/scripts/patch-zod-buffer.ts",
|
"db:generate": "prisma generate --schema=packages/db/prisma/schema.prisma && ts-node packages/db/scripts/patch-zod-buffer.ts",
|
||||||
"db:migrate": "dotenv -e packages/db/.env -- prisma migrate dev --schema=packages/db/prisma/schema.prisma",
|
"db:migrate": "dotenv -e packages/db/.env -- prisma migrate dev --schema=packages/db/prisma/schema.prisma",
|
||||||
"db:seed": "prisma db seed --schema=packages/db/prisma/schema.prisma",
|
"db:seed": "prisma db seed --schema=packages/db/prisma/schema.prisma",
|
||||||
"setup:env": "shx cp packages/db/prisma/.env.example packages/db/prisma/.env && shx cp apps/Frontend/.env.example apps/Frontend/.env && shx cp apps/Backend/.env.example apps/Backend/.env && shx cp apps/PaymentOCRService/.env.example apps/PaymentOCRService/.env",
|
"setup:env": "shx cp packages/db/prisma/.env.example packages/db/prisma/.env && shx cp apps/Frontend/.env.example apps/Frontend/.env && shx cp apps/Backend/.env.example apps/Backend/.env && shx cp apps/PatientDataExtractorService/.env.example apps/PatientDataExtractorService/.env && shx cp apps/SeleniumService/.env.example apps/SeleniumService/.env && shx cp apps/PaymentOCRService/.env.example apps/PaymentOCRService/.env",
|
||||||
"postinstall": "npm --prefix apps/PatientDataExtractorService run postinstall && npm --prefix apps/PaymentOCRService run postinstall"
|
"postinstall": "npm --prefix apps/PatientDataExtractorService run postinstall && npm --prefix apps/PaymentOCRService run postinstall"
|
||||||
},
|
},
|
||||||
"prisma": {
|
"prisma": {
|
||||||
|
|||||||
Reference in New Issue
Block a user