Plan Done, Dockerization and cleanup
Build & Push Docker Images / test-backend (push) Successful in 55s
Build & Push Docker Images / test-frontend (push) Successful in 43s
Run Tests / test-backend (push) Successful in 48s
Run Tests / test-frontend (push) Successful in 42s
Build & Push Docker Images / build-backend (push) Failing after 4m7s
Build & Push Docker Images / build-frontend (push) Failing after 32s
Build & Push Docker Images / test-backend (push) Successful in 55s
Build & Push Docker Images / test-frontend (push) Successful in 43s
Run Tests / test-backend (push) Successful in 48s
Run Tests / test-frontend (push) Successful in 42s
Build & Push Docker Images / build-backend (push) Failing after 4m7s
Build & Push Docker Images / build-frontend (push) Failing after 32s
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
PORT=3001
|
||||
SESSION_SECRET=change-me-in-production
|
||||
CORS_ORIGIN=http://localhost:3000
|
||||
FARMACIAS_WEBHOOK_URL=
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
@@ -0,0 +1,8 @@
|
||||
FROM node:18-alpine
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci --omit=dev
|
||||
COPY . .
|
||||
RUN mkdir -p /app/data
|
||||
EXPOSE 3001
|
||||
CMD ["node", "server.js"]
|
||||
@@ -1,12 +1,80 @@
|
||||
import { describe, test, expect, beforeEach } from '@jest/globals'
|
||||
import { jest } from '@jest/globals'
|
||||
|
||||
describe('Server', () => {
|
||||
test('server module can be imported', () => {
|
||||
// Basic sanity test - just checking the module loads
|
||||
expect(typeof describe).toBe('function')
|
||||
})
|
||||
jest.unstable_mockModule('../cima-service.js', () => ({
|
||||
searchMedicines: jest.fn(async () => []),
|
||||
getMedicineDetails: jest.fn(async () => null),
|
||||
}))
|
||||
|
||||
test('1 + 1 equals 2', () => {
|
||||
expect(1 + 1).toBe(2)
|
||||
jest.unstable_mockModule('../farmacias-webhook-import.js', () => ({
|
||||
runFarmaciaWebhookImport: jest.fn(async () => ({})),
|
||||
DEFAULT_FARMACIAS_WEBHOOK: '',
|
||||
importPharmaciesFromRows: jest.fn(async () => ({})),
|
||||
}))
|
||||
|
||||
jest.unstable_mockModule('../../API/index.js', () => ({
|
||||
fetchPharmaciesExternal: jest.fn(async () => []),
|
||||
}))
|
||||
|
||||
process.env.DATABASE_PATH = ':memory:'
|
||||
process.env.NODE_ENV = 'test'
|
||||
|
||||
const { default: supertest } = await import('supertest')
|
||||
const { app, initDatabase, db } = await import('../server.js')
|
||||
const { default: bcrypt } = await import('bcrypt')
|
||||
|
||||
beforeAll(async () => {
|
||||
await initDatabase()
|
||||
const hash = await bcrypt.hash('testpass', 10)
|
||||
await new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
'INSERT INTO users (username, password_hash) VALUES (?, ?)',
|
||||
['testadmin', hash],
|
||||
(err) => (err ? reject(err) : resolve())
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Medicine search', () => {
|
||||
test('GET /api/medicines/search with empty q returns []', async () => {
|
||||
const res = await supertest(app).get('/api/medicines/search?q=')
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.body).toEqual([])
|
||||
})
|
||||
|
||||
test('GET /api/medicines/search with short q returns array', async () => {
|
||||
const res = await supertest(app).get('/api/medicines/search?q=a')
|
||||
expect(res.status).toBe(200)
|
||||
expect(Array.isArray(res.body)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Authentication', () => {
|
||||
test('POST /api/auth/login with wrong creds returns 401', async () => {
|
||||
const res = await supertest(app)
|
||||
.post('/api/auth/login')
|
||||
.send({ username: 'nobody', password: 'wrong' })
|
||||
expect(res.status).toBe(401)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Admin routes', () => {
|
||||
test('GET /api/admin/medicines without auth returns 401', async () => {
|
||||
const res = await supertest(app).get('/api/admin/medicines')
|
||||
expect(res.status).toBe(401)
|
||||
})
|
||||
|
||||
test('POST /api/admin/pharmacies with valid auth returns 201', async () => {
|
||||
const agent = supertest.agent(app)
|
||||
const login = await agent
|
||||
.post('/api/auth/login')
|
||||
.send({ username: 'testadmin', password: 'testpass' })
|
||||
expect(login.status).toBe(200)
|
||||
|
||||
const res = await agent.post('/api/admin/pharmacies').send({
|
||||
name: 'Test Pharmacy',
|
||||
address: 'Test Street 1',
|
||||
})
|
||||
expect(res.status).toBe(201)
|
||||
expect(res.body).toMatchObject({ name: 'Test Pharmacy', address: 'Test Street 1' })
|
||||
})
|
||||
})
|
||||
|
||||
Generated
+188
-6
@@ -11,14 +11,17 @@
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"connect-sqlite3": "^0.9.16",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^8.5.2",
|
||||
"express-session": "^1.17.3",
|
||||
"redis": "^4.6.0",
|
||||
"sqlite3": "^5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^29.7.0"
|
||||
"jest": "^29.7.0",
|
||||
"supertest": "^7.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
@@ -1048,6 +1051,18 @@
|
||||
"set-blocking": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
|
||||
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^14.21.3 || >=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@npmcli/fs": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz",
|
||||
@@ -1074,6 +1089,15 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@paralleldrive/cuid2": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz",
|
||||
"integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/bloom": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz",
|
||||
@@ -1459,6 +1483,12 @@
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/asap": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
@@ -2064,12 +2094,32 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/component-emitter": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
|
||||
"integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/connect-sqlite3": {
|
||||
"version": "0.9.16",
|
||||
"resolved": "https://registry.npmjs.org/connect-sqlite3/-/connect-sqlite3-0.9.16.tgz",
|
||||
"integrity": "sha512-2gqo0QmcBBL8p8+eqpBETn7RgM/PaoKvpQGl8PfjEgwlr0VuMYNMxRJRrRCo3KR3fxMYeSsCw2tGNG0JKN9Nvg==",
|
||||
"dependencies": {
|
||||
"sqlite3": "^5.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.x"
|
||||
}
|
||||
},
|
||||
"node_modules/console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||
@@ -2119,6 +2169,12 @@
|
||||
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookiejar": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
|
||||
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
@@ -2280,6 +2336,16 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dezalgo": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asap": "^2.0.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/diff-sequences": {
|
||||
"version": "29.6.3",
|
||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
|
||||
@@ -2603,6 +2669,23 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/express-rate-limit": {
|
||||
"version": "8.5.2",
|
||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz",
|
||||
"integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==",
|
||||
"dependencies": {
|
||||
"ip-address": "^10.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/express-rate-limit"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"express": ">= 4.11"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session": {
|
||||
"version": "1.18.2",
|
||||
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz",
|
||||
@@ -2629,6 +2712,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-safe-stringify": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fb-watchman": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
|
||||
@@ -2726,6 +2815,23 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/formidable": {
|
||||
"version": "3.5.4",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz",
|
||||
"integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"dezalgo": "^1.0.4",
|
||||
"once": "^1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/tunnckoCore/commissions"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@@ -3236,11 +3342,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ip-address": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
|
||||
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
|
||||
"integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
@@ -5682,6 +5786,84 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz",
|
||||
"integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"component-emitter": "^1.3.1",
|
||||
"cookiejar": "^2.1.4",
|
||||
"debug": "^4.3.7",
|
||||
"fast-safe-stringify": "^2.1.1",
|
||||
"form-data": "^4.0.5",
|
||||
"formidable": "^3.5.4",
|
||||
"methods": "^1.1.2",
|
||||
"mime": "2.6.0",
|
||||
"qs": "^6.14.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent/node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/superagent/node_modules/mime": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
|
||||
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/supertest": {
|
||||
"version": "7.2.2",
|
||||
"resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz",
|
||||
"integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cookie-signature": "^1.2.2",
|
||||
"methods": "^1.1.2",
|
||||
"superagent": "^10.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supertest/node_modules/cookie-signature": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
|
||||
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
|
||||
+10
-8
@@ -12,22 +12,24 @@
|
||||
"migrate": "node migrate.js",
|
||||
"reset-db": "bash reset-db.sh",
|
||||
"import-farmacias": "node import-farmacias.js",
|
||||
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
|
||||
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --forceExit"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"cors": "^2.8.5",
|
||||
"sqlite3": "^5.1.6",
|
||||
"express-session": "^1.17.3",
|
||||
"axios": "^1.6.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"connect-sqlite3": "^0.9.16",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^8.5.2",
|
||||
"express-session": "^1.17.3",
|
||||
"redis": "^4.6.0",
|
||||
"axios": "^1.6.0"
|
||||
"sqlite3": "^5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^29.7.0"
|
||||
"jest": "^29.7.0",
|
||||
"supertest": "^7.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+30
-55
@@ -5,7 +5,9 @@ import { promisify } from 'util';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import session from 'express-session';
|
||||
import connectSqlite3 from 'connect-sqlite3';
|
||||
import bcrypt from 'bcrypt';
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import { searchMedicines, getMedicineDetails } from './cima-service.js';
|
||||
import { runFarmaciaWebhookImport, DEFAULT_FARMACIAS_WEBHOOK, importPharmaciesFromRows } from './farmacias-webhook-import.js';
|
||||
import { fetchPharmaciesExternal } from '../API/index.js';
|
||||
@@ -18,25 +20,36 @@ const PORT = process.env.PORT || 3001;
|
||||
|
||||
// Configure CORS with credentials
|
||||
app.use(cors({
|
||||
origin: 'http://localhost:3000',
|
||||
origin: process.env.CORS_ORIGIN || 'http://localhost:3000',
|
||||
credentials: true
|
||||
}));
|
||||
app.use(express.json());
|
||||
|
||||
// Configure session
|
||||
app.use(session({
|
||||
const SQLiteStore = connectSqlite3(session);
|
||||
|
||||
const sessionConfig = {
|
||||
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
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
httpOnly: true,
|
||||
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
sessionConfig.store = new SQLiteStore({ db: 'sessions.sqlite', dir: __dirname });
|
||||
}
|
||||
|
||||
// Configure session
|
||||
app.use(session(sessionConfig));
|
||||
|
||||
const searchLimiter = rateLimit({ windowMs: 60_000, max: 30, standardHeaders: true, legacyHeaders: false });
|
||||
const loginLimiter = rateLimit({ windowMs: 60_000, max: 5, standardHeaders: true, legacyHeaders: false });
|
||||
|
||||
// Database setup
|
||||
const dbPath = path.join(__dirname, 'database.sqlite');
|
||||
const dbPath = process.env.DATABASE_PATH || path.join(__dirname, 'database.sqlite');
|
||||
const db = new sqlite3.Database(dbPath);
|
||||
|
||||
// Custom wrapper to get lastID from db.run
|
||||
@@ -67,17 +80,6 @@ async function initDatabase() {
|
||||
)
|
||||
`);
|
||||
|
||||
// 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(`
|
||||
@@ -93,8 +95,6 @@ async function initDatabase() {
|
||||
)
|
||||
`);
|
||||
|
||||
// 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
|
||||
@@ -116,7 +116,7 @@ async function initDatabase() {
|
||||
// API Routes
|
||||
|
||||
// Search medicines using CIMA API with Redis cache
|
||||
app.get('/api/medicines/search', async (req, res) => {
|
||||
app.get('/api/medicines/search', searchLimiter, async (req, res) => {
|
||||
try {
|
||||
const query = req.query.q || '';
|
||||
|
||||
@@ -206,7 +206,7 @@ const requireAuth = (req, res, next) => {
|
||||
// ========== AUTHENTICATION ROUTES ==========
|
||||
|
||||
// Login endpoint
|
||||
app.post('/api/auth/login', async (req, res) => {
|
||||
app.post('/api/auth/login', loginLimiter, async (req, res) => {
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
|
||||
@@ -584,34 +584,6 @@ app.get('/api/admin/medicines', requireAuth, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 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 {
|
||||
@@ -718,10 +690,13 @@ app.delete('/api/admin/pharmacy-medicines/:id', requireAuth, async (req, res) =>
|
||||
}
|
||||
});
|
||||
|
||||
// Start server
|
||||
initDatabase().then(() => {
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running on http://localhost:${PORT}`);
|
||||
});
|
||||
});
|
||||
export { app, initDatabase, db };
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
initDatabase().then(() => {
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running on http://localhost:${PORT}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user