11 Commits

Author SHA1 Message Date
Ichitux 5c782dede7 Merge pull request 'Background fixes & improvements' (#37) from mobile_app_hotfixes into main
Build & Push Docker Images / Detect Changes (push) Successful in 12s
Build & Push Docker Images / Backend Tests (push) Has been skipped
Build & Push Docker Images / Frontend Tests (push) Has been skipped
Build & Push Docker Images / Build Backend (push) Has been skipped
Build & Push Docker Images / Build Frontend (push) Has been skipped
Build & Push Docker Images / Deploy (push) Has been skipped
Reviewed-on: #37
2026-07-14 17:56:19 +00:00
Ichitux ff387e3d20 Background fixes & improvements
Run Tests on Branches / Detect Changes (push) Successful in 14s
Run Tests on Branches / Backend Tests (push) Has been skipped
Run Tests on Branches / Frontend Tests (push) Has been skipped
Run Tests on Branches / Frontend Mobile Tests (push) Successful in 1m52s
2026-07-14 19:52:55 +02:00
Ichitux 58c1fb5bb6 Merge pull request 'fix: regenerate backend package-lock.json — sync OTel dependencies' (#36) from fix/backend-lockfile-sync into main
Build & Push Docker Images / Frontend Tests (push) Has been skipped
Build & Push Docker Images / Detect Changes (push) Successful in 11s
Build & Push Docker Images / Backend Tests (push) Successful in 1m36s
Build & Push Docker Images / Build Backend (push) Successful in 3m7s
Build & Push Docker Images / Build Frontend (push) Has been skipped
Build & Push Docker Images / Deploy (push) Successful in 1m5s
Reviewed-on: #36
2026-07-14 12:21:47 +00:00
Antoni Nuñez Romeu e83f32324f fix: regenerate backend package-lock.json — sync OTel dependencies
Run Tests on Branches / Backend Tests (push) Successful in 1m37s
Run Tests on Branches / Frontend Tests (push) Has been skipped
Run Tests on Branches / Frontend Mobile Tests (push) Has been skipped
Run Tests on Branches / Detect Changes (push) Successful in 12s
2026-07-14 14:16:57 +02:00
Antoni Nuñez Romeu 59f81c9203 Merge branch 'feat/observability-metrics-monitoring'
Build & Push Docker Images / Detect Changes (push) Successful in 11s
Build & Push Docker Images / Frontend Tests (push) Has been skipped
Build & Push Docker Images / Backend Tests (push) Successful in 1m38s
Build & Push Docker Images / Build Backend (push) Failing after 35s
Build & Push Docker Images / Build Frontend (push) Has been skipped
Build & Push Docker Images / Deploy (push) Has been skipped
2026-07-14 13:44:07 +02:00
Antoni Nuñez Romeu 809d4b8a7f Fixed backend test
Run Tests on Branches / Detect Changes (push) Successful in 12s
Run Tests on Branches / Frontend Tests (push) Has been skipped
Run Tests on Branches / Frontend Mobile Tests (push) Successful in 1m34s
Run Tests on Branches / Backend Tests (push) Successful in 1m36s
2026-07-14 13:38:16 +02:00
Antoni Nuñez Romeu e281bd4fa8 fix: skip Redis connection in test mode — prevents CI hang
Run Tests on Branches / Frontend Mobile Tests (push) Successful in 1m58s
Run Tests on Branches / Detect Changes (push) Successful in 12s
Run Tests on Branches / Backend Tests (push) Failing after 1m58s
Run Tests on Branches / Frontend Tests (push) Has been skipped
The top-level await redisClient.connect() in redis-client.js was
blocking module import when no Redis server is available (CI env).
Add NODE_ENV guard + reconnect strategy with max retries.
2026-07-14 13:25:17 +02:00
Antoni Nuñez Romeu 52b5b72cfb ci: optimize test performance — from ~3h to 3.5s
- Lazy-load tesseract.js only in OCR route (skips 30MB WASM download)
- Conditional tracing.js import (skip OTel gRPC in test mode)
- Remove unused barcode-detector import
- Add testTimeout/openHandlesTimeout to jest.config.js
- Add --forceExitTimeout=30000 to test script
- Split CI test jobs into parallel workflows (backend/frontend/mobile)
- Add timeout-minutes: 15 to all test jobs
- Use npm ci --ignore-scripts + selective native rebuild
2026-07-14 13:24:19 +02:00
Ichitux 89cb8ee2fb Merge pull request 'ci: optimize test performance — from ~3h to 3.5s' (#35) from ci/optimize-test-performance into main
Build & Push Docker Images / Detect Changes (push) Successful in 12s
Build & Push Docker Images / Backend Tests (push) Successful in 1m36s
Build & Push Docker Images / Frontend Tests (push) Has been skipped
Build & Push Docker Images / Build Backend (push) Successful in 3m9s
Build & Push Docker Images / Build Frontend (push) Has been skipped
Build & Push Docker Images / Deploy (push) Successful in 1m2s
Reviewed-on: #35
2026-07-14 11:08:41 +00:00
Antoni Nuñez Romeu bd7fe56330 fix: skip Redis connection in test mode — prevents CI hang
Run Tests on Branches / Detect Changes (push) Successful in 13s
Run Tests on Branches / Backend Tests (push) Successful in 2m9s
Run Tests on Branches / Frontend Tests (push) Has been skipped
Run Tests on Branches / Frontend Mobile Tests (push) Has been skipped
The top-level await redisClient.connect() in redis-client.js was
blocking module import when no Redis server is available (CI env).
Add NODE_ENV guard + reconnect strategy with max retries.
2026-07-14 13:03:46 +02:00
Antoni Nuñez Romeu e3db56715b ci: optimize test performance — from ~3h to 3.5s
Run Tests on Branches / Frontend Tests (push) Has been cancelled
Run Tests on Branches / Frontend Mobile Tests (push) Has been cancelled
Run Tests on Branches / Backend Tests (push) Has been cancelled
Run Tests on Branches / Detect Changes (push) Successful in 13s
- Lazy-load tesseract.js only in OCR route (skips 30MB WASM download)
- Conditional tracing.js import (skip OTel gRPC in test mode)
- Remove unused barcode-detector import
- Add testTimeout/openHandlesTimeout to jest.config.js
- Add --forceExitTimeout=30000 to test script
- Split CI test jobs into parallel workflows (backend/frontend/mobile)
- Add timeout-minutes: 15 to all test jobs
- Use npm ci --ignore-scripts + selective native rebuild
2026-07-14 12:42:02 +02:00
14 changed files with 809 additions and 1001 deletions
+7 -3
View File
@@ -31,6 +31,7 @@ jobs:
needs: detect-changes needs: detect-changes
if: needs.detect-changes.outputs.backend == 'true' if: needs.detect-changes.outputs.backend == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Node.js - name: Setup Node.js
@@ -39,15 +40,18 @@ jobs:
node-version: '22' node-version: '22'
cache: 'npm' cache: 'npm'
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci --ignore-scripts
- name: Install backend native deps
run: npm rebuild sqlite3 bcrypt --workspace=farma-clic-backend
- name: Run tests - name: Run tests
run: npm test --workspace=farma-clic-backend -- --ci run: npm test --workspace=farma-clic-backend
test-frontend: test-frontend:
name: Frontend Tests name: Frontend Tests
needs: detect-changes needs: detect-changes
if: needs.detect-changes.outputs.frontend == 'true' if: needs.detect-changes.outputs.frontend == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Node.js - name: Setup Node.js
@@ -56,7 +60,7 @@ jobs:
node-version: '22' node-version: '22'
cache: 'npm' cache: 'npm'
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci --ignore-scripts
- name: Run tests - name: Run tests
run: npm test --workspace=farma-clic-frontend -- --run --reporter=basic run: npm test --workspace=farma-clic-frontend -- --run --reporter=basic
+62 -22
View File
@@ -6,42 +6,82 @@ on:
- 'main' - 'main'
jobs: jobs:
test: detect-changes:
name: Run Tests name: Detect Changes
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
frontend-mobile: ${{ steps.filter.outputs.frontend-mobile }}
steps: steps:
- name: Checkout Code - uses: actions/checkout@v4
uses: actions/checkout@v4 - uses: dorny/paths-filter@v3
id: filter
with: with:
fetch-depth: 2 filters: |
backend:
- name: Detect Changes - 'apps/backend/**'
id: changes - 'packages/**'
run: | frontend:
BASE=${{ github.event.before }} - 'apps/frontend/**'
[ -z "$BASE" ] || [ "$BASE" = "0000000000000000000000000000000000000000" ] && BASE=$(git rev-parse HEAD~1) - 'packages/**'
echo "backend=$(git diff --name-only "$BASE"...HEAD | grep -qE '^(apps/backend/|packages/)' && echo true || echo false)" >> "$GITHUB_OUTPUT" frontend-mobile:
echo "frontend=$(git diff --name-only "$BASE"...HEAD | grep -qE '^(apps/frontend/|packages/)' && echo true || echo false)" >> "$GITHUB_OUTPUT" - 'apps/frontend-mobile/**'
echo "frontend-mobile=$(git diff --name-only "$BASE"...HEAD | grep -qE '^(apps/frontend-mobile/|packages/)' && echo true || echo false)" >> "$GITHUB_OUTPUT" - 'packages/**'
test-backend:
name: Backend Tests
needs: detect-changes
if: needs.detect-changes.outputs.backend == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '22' node-version: '22'
cache: 'npm' cache: 'npm'
- name: Install dependencies
- name: Install Dependencies run: npm ci --ignore-scripts
run: npm ci - name: Install backend native deps
run: npm rebuild sqlite3 bcrypt --workspace=farma-clic-backend
- name: Run Backend Tests - name: Run Backend Tests
if: steps.changes.outputs.backend == 'true' run: npm test --workspace=farma-clic-backend
run: npm test --workspace=farma-clic-backend -- --ci
test-frontend:
name: Frontend Tests
needs: detect-changes
if: needs.detect-changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Install dependencies
run: npm ci --ignore-scripts
- name: Run Frontend Tests - name: Run Frontend Tests
if: steps.changes.outputs.frontend == 'true'
run: npm test --workspace=farma-clic-frontend -- --run --reporter=basic run: npm test --workspace=farma-clic-frontend -- --run --reporter=basic
test-frontend-mobile:
name: Frontend Mobile Tests
needs: detect-changes
if: needs.detect-changes.outputs.frontend-mobile == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Install dependencies
run: npm ci --ignore-scripts
- name: Run Frontend Mobile Tests - name: Run Frontend Mobile Tests
if: steps.changes.outputs.frontend-mobile == 'true'
run: npm test --workspace=frontend-mobile -- --ci run: npm test --workspace=frontend-mobile -- --ci
continue-on-error: true continue-on-error: true
+1
View File
@@ -3,6 +3,7 @@ dist/
dev-dist/ dev-dist/
build/ build/
.turbo/ .turbo/
.mimocode/
*.sqlite *.sqlite
*.sqlite3 *.sqlite3
.env .env
+1 -1
View File
@@ -1 +1 @@
{"pid":10782,"startedAt":1783578681151} {"pid":568607,"startedAt":1784046408782}
+4
View File
@@ -3,4 +3,8 @@ export default {
transform: {}, transform: {},
moduleFileExtensions: ['js', 'json'], moduleFileExtensions: ['js', 'json'],
testMatch: ['**/__tests__/**/*.test.js'], testMatch: ['**/__tests__/**/*.test.js'],
testTimeout: 15000,
openHandlesTimeout: 5000,
// Run test files in parallel (not individual tests within a file)
// workerIdleMemoryLimit: '512MB',
} }
+676 -963
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -12,7 +12,7 @@
"migrate": "node migrate.js", "migrate": "node migrate.js",
"reset-db": "bash reset-db.sh", "reset-db": "bash reset-db.sh",
"import-farmacias": "node import-farmacias.js", "import-farmacias": "node import-farmacias.js",
"test": "NODE_OPTIONS='--experimental-vm-modules' npx jest --forceExit" "test": "NODE_OPTIONS='--experimental-vm-modules' npx jest --ci --forceExit --forceExitTimeout=30000"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
+9 -3
View File
@@ -5,7 +5,11 @@ import * as appMetrics from './src/metrics.js';
const redisClient = createClient({ const redisClient = createClient({
socket: { socket: {
host: process.env.REDIS_HOST || 'localhost', host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379 port: process.env.REDIS_PORT || 6379,
reconnectStrategy: (retries) => {
if (retries > 10) return new Error('Redis max retries reached');
return Math.min(retries * 100, 3000);
}
}, },
password: process.env.REDIS_PASSWORD || undefined password: process.env.REDIS_PASSWORD || undefined
}); });
@@ -42,7 +46,9 @@ redisClient.setEx = async (...args) => {
} }
}; };
// Connect to Redis // Connect to Redis — skip in test (services are mocked) or when REDIS_URL is unset
await redisClient.connect(); if (process.env.NODE_ENV !== 'test') {
await redisClient.connect();
}
export default redisClient; export default redisClient;
+5 -3
View File
@@ -1,5 +1,8 @@
// OpenTelemetry SDK — must be imported first to instrument http, express, pg, etc. // OpenTelemetry SDK — must be imported first to instrument http, express, pg, etc.
import './src/tracing.js'; // Skipped in test to avoid gRPC connection attempts to a non-existent collector.
if (process.env.NODE_ENV !== 'test') {
await import('./src/tracing.js');
}
import express from 'express'; import express from 'express';
import redisClient from './redis-client.js'; import redisClient from './redis-client.js';
@@ -19,8 +22,6 @@ import webpush from 'web-push';
import pino from 'pino'; import pino from 'pino';
import pinoHttp from 'pino-http'; import pinoHttp from 'pino-http';
import multer from 'multer'; import multer from 'multer';
import { createWorker } from 'tesseract.js';
import { BarcodeDetector } from 'barcode-detector';
import { searchMedicines, getMedicineDetails, searchOTC } from './cima-service.js'; import { searchMedicines, getMedicineDetails, searchOTC } from './cima-service.js';
import { searchBabyProducts, getBabyProductDetails } from './off-service.js'; import { searchBabyProducts, getBabyProductDetails } from './off-service.js';
import { runFarmaciaWebhookImport, DEFAULT_FARMACIAS_WEBHOOK, importPharmaciesFromRows } from './farmacias-webhook-import.js'; import { runFarmaciaWebhookImport, DEFAULT_FARMACIAS_WEBHOOK, importPharmaciesFromRows } from './farmacias-webhook-import.js';
@@ -914,6 +915,7 @@ app.post('/api/tsi/ocr', upload.single('photo'), async (req, res) => {
console.log('[OCR] Processing image, size:', req.file.size, 'bytes'); console.log('[OCR] Processing image, size:', req.file.size, 'bytes');
const { createWorker } = await import('tesseract.js');
const worker = await createWorker('spa+eng'); const worker = await createWorker('spa+eng');
const { data } = await worker.recognize(req.file.buffer); const { data } = await worker.recognize(req.file.buffer);
await worker.terminate(); await worker.terminate();
@@ -55,6 +55,8 @@ export default function TabLayout() {
fontWeight: '600', fontWeight: '600',
fontSize: isTablet ? 20 : 17, fontSize: isTablet ? 20 : 17,
}, },
sceneContainerStyle: { backgroundColor: 'transparent' },
sceneStyle: { backgroundColor: 'transparent' },
}} }}
> >
<Tabs.Screen <Tabs.Screen
+17 -3
View File
@@ -50,6 +50,7 @@ function RootLayoutInner() {
headerStyle: { backgroundColor: colors.card }, headerStyle: { backgroundColor: colors.card },
headerTintColor: colors.primary, headerTintColor: colors.primary,
headerTitleStyle: { color: colors.text, fontWeight: '600' }, headerTitleStyle: { color: colors.text, fontWeight: '600' },
contentStyle: { backgroundColor: 'transparent' },
}} }}
> >
<Stack.Screen name="(tabs)" options={{ headerShown: false }} /> <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
@@ -99,7 +100,7 @@ function ThemedBackground({ children }: { children: React.ReactNode }) {
resizeMode="cover" resizeMode="cover"
/> />
<View style={[styles.overlay, isDark && styles.overlayDark]} pointerEvents="none" /> <View style={[styles.overlay, isDark && styles.overlayDark]} pointerEvents="none" />
<View style={styles.content}> <View style={styles.content} pointerEvents="auto">
{children} {children}
</View> </View>
</View> </View>
@@ -127,17 +128,30 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
bgImage: { bgImage: {
...StyleSheet.absoluteFillObject, position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
width: '100%',
height: '100%',
opacity: 0.55, opacity: 0.55,
zIndex: 0,
}, },
overlay: { overlay: {
...StyleSheet.absoluteFillObject, position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
backgroundColor: 'rgba(255, 252, 245, 0.48)', backgroundColor: 'rgba(255, 252, 245, 0.48)',
zIndex: 1,
}, },
overlayDark: { overlayDark: {
backgroundColor: 'rgba(0, 0, 0, 0.25)', backgroundColor: 'rgba(0, 0, 0, 0.25)',
}, },
content: { content: {
flex: 1, flex: 1,
zIndex: 2,
}, },
}); });
+21
View File
@@ -0,0 +1,21 @@
const { getDefaultConfig } = require("expo/metro-config");
const path = require("path");
const projectRoot = __dirname;
const monorepoRoot = path.resolve(projectRoot, "../..");
const config = getDefaultConfig(projectRoot);
// Watch the monorepo root so Metro sees all workspace packages
config.watchFolders = [monorepoRoot];
// Resolve modules from both the workspace and root node_modules
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, "node_modules"),
path.resolve(monorepoRoot, "node_modules"),
];
// Ensure symlinks are followed (npm workspaces use symlinks)
config.resolver.unstable_enableSymlinks = true;
module.exports = config;
+1 -1
View File
@@ -4,6 +4,7 @@
"main": "expo-router/entry", "main": "expo-router/entry",
"dependencies": { "dependencies": {
"@expo/vector-icons": "^15.0.2", "@expo/vector-icons": "^15.0.2",
"@grafana/faro-react-native": "^1.3.0",
"@react-native-async-storage/async-storage": "2.2.0", "@react-native-async-storage/async-storage": "2.2.0",
"@tanstack/react-query": "^5.101.2", "@tanstack/react-query": "^5.101.2",
"axios": "^1.18.1", "axios": "^1.18.1",
@@ -13,7 +14,6 @@
"expo-constants": "~57.0.3", "expo-constants": "~57.0.3",
"expo-dev-client": "~57.0.5", "expo-dev-client": "~57.0.5",
"expo-device": "~7.0.2", "expo-device": "~7.0.2",
"@grafana/faro-react-native": "^1.2.1",
"expo-image-picker": "~57.0.2", "expo-image-picker": "~57.0.2",
"expo-linking": "~57.0.1", "expo-linking": "~57.0.1",
"expo-local-authentication": "~57.0.0", "expo-local-authentication": "~57.0.0",
+2 -1
View File
@@ -138,7 +138,7 @@
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@expo/vector-icons": "^15.0.2", "@expo/vector-icons": "^15.0.2",
"@grafana/faro-react-native": "^1.2.1", "@grafana/faro-react-native": "^1.3.0",
"@react-native-async-storage/async-storage": "2.2.0", "@react-native-async-storage/async-storage": "2.2.0",
"@tanstack/react-query": "^5.101.2", "@tanstack/react-query": "^5.101.2",
"axios": "^1.18.1", "axios": "^1.18.1",
@@ -416,6 +416,7 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/@grafana/faro-react-native/-/faro-react-native-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@grafana/faro-react-native/-/faro-react-native-1.3.0.tgz",
"integrity": "sha512-KeohD3S2xmiukL5YW3mOGDqvFiwpD9IzJ2EtZ50ktEsnA+6EMt2AtJ8RzJfAamGTYqemBpDiw51rqB4ID+C2xg==", "integrity": "sha512-KeohD3S2xmiukL5YW3mOGDqvFiwpD9IzJ2EtZ50ktEsnA+6EMt2AtJ8RzJfAamGTYqemBpDiw51rqB4ID+C2xg==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"@grafana/faro-core": "^2.7.0", "@grafana/faro-core": "^2.7.0",
"@react-native-async-storage/async-storage": "^1.21.0", "@react-native-async-storage/async-storage": "^1.21.0",