chore: sync all local changes and new files

- Update next.config.ts
- Add netlify.toml, Docker configs, e2e tests
- Add env templates, docs (README, ROADMAP, TODO)
- Add vitest/playwright test configs
- Add component and lib tests
- Add install script, portainer env template
- Update .gitignore (tsbuildinfo, deno.lock, dev.db)
This commit is contained in:
FalahMobile
2026-07-02 20:01:48 +08:00
parent 2e66def057
commit 61baf3ac5d
25 changed files with 2456 additions and 2 deletions
+111
View File
@@ -0,0 +1,111 @@
# Falah OS v1.3 — Environment Variables
# Copy to .env and fill in all values before running.
# Generate secrets with: openssl rand -base64 32
# =============================================================================
# REQUIRED — must be set before starting
# =============================================================================
# JWT signing secret — min 32 random characters
JWT_SECRET=
# Encryption key for sensitive data — min 32 random characters
ENCRYPTION_KEY=
# Admin secret for privileged operations
ADMIN_SECRET=
# PostgreSQL password
POSTGRES_PASSWORD=
# Redis password
REDIS_PASSWORD=
# iBaaS API Key for EE Gateway
IBAAS_API_KEY=falah-os-ibaas-key-2026
# =============================================================================
# POLAR — required for subscription payments (polar.sh)
# =============================================================================
# Get from polar.sh > Settings > Developers > Access Tokens
POLAR_ACCESS_TOKEN=
# Get from polar.sh > Settings > Webhooks > your endpoint secret
POLAR_WEBHOOK_SECRET=
# App URL for Polar redirect URLs
NEXT_PUBLIC_APP_URL=http://localhost:3000
# =============================================================================
# AI — required for Nur AI coaching
# =============================================================================
OPENCORE_URL=https://opencode.ai/zen/go/v1/chat/completions
OPENCORE_API_KEY=
# =============================================================================
# OPTIONAL — have sensible defaults
# =============================================================================
NODE_ENV=production
# Domain name and protocol for URL generation
DOMAIN=
PROTOCOL=https
# Protocol fee taken by Falah OS (default 1.5%)
PROTOCOL_FEE_PERCENT=1.5
# Shariah rules enforced by RAMZ (comma-separated, no spaces)
SHARIAH_RULES=NO_RIBA,NO_GHARAR,NO_MAYSIR,HALAL_COMMODITY,ASSET_BACKED,MUTUAL_CONSENT,NO_BAI_INAH
# Set to true only in a controlled test environment
ENABLE_CHAOS_TESTING=false
# =============================================================================
# LOGGING (optional)
# =============================================================================
LOG_LEVEL=info
LOG_FORMAT=json
# =============================================================================
# POSTGRES (optional overrides)
# =============================================================================
POSTGRES_USER=postgres
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
# Derived from the above; override per-service database name as needed
DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/falahdb
# =============================================================================
# REDIS (optional overrides)
# =============================================================================
REDIS_HOST=redis
REDIS_PORT=6379
# =============================================================================
# FEATURE FLAGS (optional)
# =============================================================================
ENABLE_MOCKNET=false
ENABLE_DEBUG=false
# =============================================================================
# MONITORING (optional)
# =============================================================================
GRAFANA_PASSWORD=
ALERT_EMAIL=
SLACK_WEBHOOK_URL=
# =============================================================================
# BACKUP (optional)
# =============================================================================
BACKUP_DIR=/backups
S3_BUCKET=
RETENTION_DAYS=30
+83
View File
@@ -0,0 +1,83 @@
# Falah OS v1.3 Production Environment Template
# Copy this to .env and fill in the values
# =============================================================================
# CORE CONFIGURATION
# =============================================================================
NODE_ENV=production
DOMAIN=falah-os.com
PROTOCOL=https
# =============================================================================
# SECURITY - GENERATE WITH: openssl rand -base64 32
# =============================================================================
JWT_SECRET=CHANGE_ME_generate_secure_random_string
ENCRYPTION_KEY=CHANGE_ME_generate_secure_random_string
ADMIN_SECRET=CHANGE_ME_generate_secure_random_string
# =============================================================================
# DATABASE - UPDATE credentials for production
# =============================================================================
POSTGRES_HOST=postgres-primary
POSTGRES_PORT=5432
POSTGRES_USER=postgres
POSTGRES_PASSWORD=CHANGE_ME_strong_password
POSTGRES_DB=falahdb
# =============================================================================
# REDIS - UPDATE password for production
# =============================================================================
REDIS_HOST=redis-master
REDIS_PORT=6379
REDIS_PASSWORD=CHANGE_ME_strong_password
# =============================================================================
# SERVICE URLs (Internal)
# =============================================================================
UMMAHID_URL=http://ummahid:3000
WALLET_URL=http://wallet:3000
RAMZ_URL=http://ramz:3000
MOCKNET_URL=http://mocknet:3000
# =============================================================================
# WALLET SERVICE
# =============================================================================
PROTOCOL_FEE_PERCENT=1.5
# =============================================================================
# RAMZ CONTRACT ENGINE
# =============================================================================
SHARIAH_RULES=NO_RIBA,NO_GHARAR,NO_MAYSIR,HALAL_COMMODITY,ASSET_BACKED,MUTUAL_CONSENT,NO_BAI_INAH
# =============================================================================
# BACKUP CONFIGURATION
# =============================================================================
BACKUP_DIR=/backups
S3_BUCKET=falah-os-backups
RETENTION_DAYS=30
# =============================================================================
# MONITORING
# =============================================================================
GRAFANA_PASSWORD=CHANGE_ME_strong_password
ALERT_EMAIL=alerts@falah-os.com
SLACK_WEBHOOK_URL=
# =============================================================================
# CDN & EXTERNAL SERVICES
# =============================================================================
CDN_API_KEY=
CDN_ZONE_ID=
# =============================================================================
# LOGGING
# =============================================================================
LOG_LEVEL=info
LOG_FORMAT=json
# =============================================================================
# FEATURE FLAGS
# =============================================================================
ENABLE_MOCKNET=false
ENABLE_CHAOS_TESTING=false
ENABLE_DEBUG=false
+39
View File
@@ -0,0 +1,39 @@
name: QA Workflow
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node 20
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run Vitest (Unit + Integration)
run: npx vitest run
- name: Install Playwright Browsers
run: npx playwright install --with-deps chromium
- name: Run Playwright (E2E)
run: npx playwright test
- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
+10
View File
@@ -43,6 +43,16 @@ coverage/
*.sql *.sql
!infrastructure/postgres/init.sql !infrastructure/postgres/init.sql
# TypeScript
*.tsbuildinfo
# Deno (not used in this project)
deno.lock
# SQLite dev database
prisma/dev.db
prisma/dev.db-journal
# Claude (workspace config, not project code) # Claude (workspace config, not project code)
.claude/* .claude/*
!.claude/commands/ !.claude/commands/
+51
View File
@@ -0,0 +1,51 @@
[submodule "falah-os"]
path = falah-os
url = https://github.com/maifors/falah-os.git
[submodule "ulp-portal"]
path = ulp-portal
url = https://github.com/maifors/ulp-portal.git
[submodule "falah-os-sdk-py"]
path = falah-os-sdk-py
url = https://github.com/maifors/falah-os-sdk-py.git
[submodule "falah-os-sdk"]
path = falah-os-sdk
url = https://github.com/maifors/falah-os-sdk.git
[submodule "falah-os-mocknet"]
path = falah-os-mocknet
url = https://github.com/maifors/falah-os-mocknet.git
[submodule "alah-os-mocknet"]
path = alah-os-mocknet
url = https://github.com/maifors/alah-os-mocknet.git
[submodule "falah-os-ummahid"]
path = falah-os-ummahid
url = https://github.com/maifors/falah-os-ummahid.git
[submodule "falah-os-ramz"]
path = falah-os-ramz
url = https://github.com/maifors/falah-os-ramz.git
[submodule "falah-os-admin"]
path = falah-os-admin
url = https://github.com/maifors/falah-os-admin.git
[submodule "falah-os-app"]
path = falah-os-app
url = https://github.com/maifors/falah-os-app.git
[submodule "falah-os-istore"]
path = falah-os-istore
url = https://github.com/maifors/falah-os-istore.git
[submodule "falah-os-dev-porta"]
path = falah-os-dev-porta
url = https://github.com/maifors/falah-os-dev-porta.git
[submodule "falah-os-landing"]
path = falah-os-landing
url = https://github.com/maifors/falah-os-landing.git
Executable
+133
View File
@@ -0,0 +1,133 @@
.PHONY: help install build up down restart logs health backup deploy clean \
ummahid-logs wallet-logs ramz-logs mocknet-logs api-logs app-logs \
db-shell db-backup db-restore redis-shell redis-flush \
stats ps setup env-check test test-watch test-coverage
# Falah OS v1.3 — Docker Compose management
# Usage: make help
help: ## Show available commands
@echo "Falah OS v1.3"
@echo ""
@echo "Usage: make [command]"
@echo ""
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}'
# ---------------------------------------------------------------------------
# Core lifecycle
# ---------------------------------------------------------------------------
install: ## Full install: generate secrets, write .env, start stack, health check
@bash install.sh
setup: ## First-time setup: copy .env.example → .env (manual secret entry)
@if [ -f .env ]; then echo ".env already exists, skipping."; else cp .env.example .env && echo "Created .env — fill in secrets before running 'make up'"; fi
env-check: ## Verify required env vars are set
@missing=0; \
for var in JWT_SECRET ENCRYPTION_KEY ADMIN_SECRET POSTGRES_PASSWORD REDIS_PASSWORD; do \
if [ -z "$$(grep -E "^$$var=.+" .env 2>/dev/null | cut -d= -f2)" ]; then \
echo "MISSING: $$var"; missing=1; \
fi; \
done; \
[ $$missing -eq 0 ] && echo "All required env vars are set." || exit 1
test: ## Run all tests
npm test
test-watch: ## Run tests in watch mode
npm run test:watch
test-coverage: ## Run tests with coverage report
npm run test:coverage
build: ## Build all service images
docker compose build
up: env-check ## Start the full stack
docker compose up -d
@echo "Waiting for services..."
@sleep 8
@$(MAKE) health
down: ## Stop and remove containers (preserves volumes)
docker compose down
restart: ## Restart all containers
docker compose restart
logs: ## Tail logs from all services
docker compose logs -f --tail=100
health: ## Run health checks against all services
@./scripts/health-check.sh
ps: ## Show container status
docker compose ps
stats: ## Live container resource usage
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}"
# ---------------------------------------------------------------------------
# Per-service logs
# ---------------------------------------------------------------------------
ummahid-logs: ## Tail Ummah ID logs
docker compose logs -f ummahid
wallet-logs: ## Tail Wallet logs
docker compose logs -f wallet
ramz-logs: ## Tail RAMZ logs
docker compose logs -f ramz
mocknet-logs: ## Tail Mock-Net logs
docker compose logs -f mocknet
falahd-logs: ## Tail falahd daemon logs
docker compose logs -f falahd
api-logs: ## Tail API Gateway logs
docker compose logs -f api-gateway
app-logs: ## Tail App logs
docker compose logs -f app
# ---------------------------------------------------------------------------
# Database
# ---------------------------------------------------------------------------
db-shell: ## Open PostgreSQL shell
docker compose exec postgres psql -U $${POSTGRES_USER:-postgres}
db-backup: ## Dump all databases to backups/
@mkdir -p backups
docker compose exec postgres pg_dumpall -U $${POSTGRES_USER:-postgres} \
| gzip > backups/postgres_$$(date +%Y%m%d_%H%M%S).sql.gz
@echo "Backup saved to backups/"
db-restore: ## Restore from backup: make db-restore FILE=backups/postgres_xxx.sql.gz
@[ -n "$(FILE)" ] || (echo "Usage: make db-restore FILE=backups/postgres_xxx.sql.gz" && exit 1)
gunzip -c $(FILE) | docker compose exec -T postgres psql -U $${POSTGRES_USER:-postgres}
# ---------------------------------------------------------------------------
# Redis
# ---------------------------------------------------------------------------
redis-shell: ## Open Redis CLI
docker compose exec redis redis-cli -a $${REDIS_PASSWORD}
redis-flush: ## Flush all Redis data (will prompt for confirmation)
@echo "WARNING: this deletes all Redis data."
@read -p "Type 'yes' to confirm: " c && [ "$$c" = "yes" ] || exit 1
docker compose exec redis redis-cli -a $${REDIS_PASSWORD} FLUSHALL
# ---------------------------------------------------------------------------
# Cleanup
# ---------------------------------------------------------------------------
clean: ## Remove containers AND volumes (destroys all data — prompts first)
@echo "WARNING: this destroys all Falah OS containers and volumes."
@read -p "Type 'yes' to confirm: " c && [ "$$c" = "yes" ] || exit 1
docker compose down -v
@echo "Done. Run 'make up' to start fresh."
+483
View File
@@ -0,0 +1,483 @@
# Falah OS Master - Repository Guide
## 📚 Overview
Falah OS Master is a monorepo structure that manages the Falah Operating System ecosystem through Git submodules. This repository contains links to all core modules and services that make up the complete Falah OS platform.
## 🏗️ Repository Structure
This project follows a **modular architecture** with the following components:
```
Falah-OS-Master/
├── .gitmodules # Submodule configuration
├── README.md # This file
├── falah-os/ # Core OS
├── ulp-portal/ # User Portal
├── falah-os-sdk-py/ # Python SDK
├── falah-os-sdk/ # Main SDK
├── falah-os-mocknet/ # Mock Network
├── alah-os-mocknet/ # Alternative Mock Network
├── falah-os-ummahid/ # UmmaID Component
├── falah-os-ramz/ # RAMZ Component
├── falah-os-admin/ # Admin Dashboard
├── falah-os-app/ # Main Application
├── falah-os-istore/ # Store Interface
├── falah-os-dev-porta/ # Developer Portal
└── falah-os-landing/ # Landing Page
```
## 📋 Module Documentation
### Core Modules
| Module | Repository | Purpose | Status |
|--------|-----------|---------|--------|
| **falah-os** | `maifors/falah-os` | Core operating system kernel and runtime | Active |
| **falah-os-sdk** | `maifors/falah-os-sdk` | Main SDK for development | Active |
| **falah-os-sdk-py** | `maifors/falah-os-sdk-py` | Python SDK wrapper | Active |
### Portal & Interface Modules
| Module | Repository | Purpose | Status |
|--------|-----------|---------|--------|
| **ulp-portal** | `maifors/ulp-portal` | User Portal Interface | Active |
| **falah-os-admin** | `maifors/falah-os-admin` | Administration Dashboard | Active |
| **falah-os-dev-porta** | `maifors/falah-os-dev-porta` | Developer Portal | Active |
| **falah-os-landing** | `maifors/falah-os-landing` | Marketing Landing Page | Active |
### Application & Service Modules
| Module | Repository | Purpose | Status |
|--------|-----------|---------|--------|
| **falah-os-app** | `maifors/falah-os-app` | Main Application | Active |
| **falah-os-istore** | `maifors/falah-os-istore` | App Store Interface | Active |
### Identity & Network Modules
| Module | Repository | Purpose | Status |
|--------|-----------|---------|--------|
| **falah-os-ummahid** | `maifors/falah-os-ummahid` | Identity Management (UmmaID) | Active |
| **falah-os-ramz** | `maifors/falah-os-ramz` | RAMZ Protocol/Service | Active |
### Testing & Infrastructure Modules
| Module | Repository | Purpose | Status |
|--------|-----------|---------|--------|
| **falah-os-mocknet** | `maifors/falah-os-mocknet` | Mock Network for Testing | Active |
| **alah-os-mocknet** | `maifors/alah-os-mocknet` | Alternative Mock Network | Active |
---
## 🚀 Getting Started
### Prerequisites
- Git (v2.13+)
- Basic Unix/Linux knowledge
- Access to all submodule repositories
### Initial Setup
#### Clone the Repository with All Submodules
```bash
# Clone with recursive submodules initialization
git clone --recurse-submodules https://github.com/Falah-Consultancy-Limited/Falah-OS-Master.git
# Navigate to the repository
cd Falah-OS-Master
```
#### If Already Cloned Without Submodules
```bash
# Initialize and fetch all submodules
git submodule update --init --recursive
# Or use the shorthand
git submodule init
git submodule update
```
#### Update All Submodules to Latest
```bash
# Fetch the latest commits from all submodules
git submodule foreach git pull origin main
# Or use the parallel version (Git 2.8+)
git submodule foreach --parallel git pull origin main
```
---
## 📖 Development Workflow (SDLC)
### Phase 1: Planning & Requirements
1. **Issue Creation**: Create GitHub issues for new features/bugs
2. **Milestone Assignment**: Assign to relevant milestones
3. **Documentation**: Update module documentation if affected
### Phase 2: Development
#### Branch Strategy (Git Flow)
```
main (production)
└── develop (staging)
├── feature/description
├── bugfix/issue-number
└── hotfix/issue-number
```
#### Creating Feature Branches
```bash
# Update main/develop
git checkout develop
git pull origin develop
# Create feature branch
git checkout -b feature/ISSUE-NUMBER-description
# For specific module
cd falah-os
git checkout -b feature/ISSUE-NUMBER-description
git push origin feature/ISSUE-NUMBER-description
```
#### Working with Submodules
```bash
# Navigate to specific module
cd falah-os-admin
# Create and checkout feature branch
git checkout -b feature/new-feature
# Make changes and commit
git add .
git commit -m "feat: add new feature"
# Push changes
git push origin feature/new-feature
# Go back to master repo
cd ..
# Update submodule reference (if needed)
git add falah-os-admin
git commit -m "chore: update falah-os-admin submodule"
git push origin feature/ISSUE-NUMBER
```
### Phase 3: Testing
#### Unit Testing
```bash
# Run tests in specific module
cd <module-name>
npm test # For Node.js projects
pytest # For Python projects
go test ./... # For Go projects
```
#### Integration Testing
```bash
# Use mock networks for integration testing
cd falah-os-mocknet
# Follow module-specific test documentation
```
#### Staging Deployment
- Deploy to staging environment
- Run end-to-end tests
- Validate across all modules
### Phase 4: Code Review & Quality
#### Pull Request Checklist
- [ ] Feature branch created from `develop`
- [ ] All tests pass locally
- [ ] Code follows project style guide
- [ ] Documentation updated
- [ ] No breaking changes
- [ ] Linked to relevant GitHub issues
#### Review Requirements
- Minimum 2 approvals required
- CI/CD pipeline passes
- No merge conflicts
- Code coverage maintained
### Phase 5: Deployment
#### Staging Release
```bash
# Create release branch
git checkout -b release/v1.x.x develop
# Update version numbers and documentation
# Test thoroughly
# Create PR to main
# After approval and merge, tag the release
git tag -a v1.x.x -m "Release version 1.x.x"
git push origin v1.x.x
```
#### Production Deployment
- Merge `release/v1.x.x` to `main`
- Tag with version number
- Deploy to production
- Update all submodules references if needed
---
## 📝 Documentation Standards
### Commit Message Format
```
<type>(<scope>): <subject>
<body>
<footer>
```
#### Types
- `feat`: A new feature
- `fix`: A bug fix
- `docs`: Documentation only changes
- `style`: Changes that don't affect code meaning
- `refactor`: Code change that neither fixes a bug nor adds a feature
- `perf`: Code change that improves performance
- `test`: Adding or updating tests
- `chore`: Changes to build process, dependencies, etc.
#### Example
```
feat(admin-dashboard): add user management panel
Implemented new user management interface in the admin panel
with the following features:
- User listing with pagination
- Create/Edit/Delete user operations
- Role assignment
Fixes #123
```
### Pull Request Template
```markdown
## Description
Provide a brief summary of the changes
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Related Issue
Closes #ISSUE_NUMBER
## Testing
Describe the tests run and results
## Screenshots (if applicable)
Add screenshots for UI changes
## Checklist
- [ ] My code follows the style guidelines
- [ ] I have performed a self-review
- [ ] I have commented complex areas
- [ ] I have updated documentation
- [ ] Tests pass locally
- [ ] No new warnings generated
```
### Module README Requirements
Each module should include:
1. **Overview**: What the module does
2. **Installation**: How to set it up
3. **Quick Start**: Basic usage example
4. **API Documentation**: For libraries/SDKs
5. **Configuration**: Setup and environment variables
6. **Testing**: How to run tests
7. **Contributing**: Contribution guidelines
8. **License**: License information
---
## 🔄 Release Management
### Versioning
All modules follow **Semantic Versioning (SemVer)**: `MAJOR.MINOR.PATCH`
- **MAJOR**: Breaking changes
- **MINOR**: New features (backward compatible)
- **PATCH**: Bug fixes (backward compatible)
### Release Cycle
1. **Development Phase** (2-4 weeks)
- Features merged to `develop`
- Daily integration testing
2. **Release Candidate Phase** (1 week)
- Release branch created: `release/v1.x.x`
- Bug fixes only
- Release notes prepared
3. **Production Release**
- Merge to `main`
- Tag release: `git tag -a v1.x.x`
- Update all dependent modules
---
## 🛠️ Common Tasks
### Sync All Submodules with Remote
```bash
git submodule foreach --recursive git fetch origin
git submodule foreach --recursive git pull origin main
```
### Check Submodule Status
```bash
git submodule status
```
### Add New Submodule
```bash
git submodule add https://github.com/maifors/new-module.git new-module
git add .gitmodules new-module
git commit -m "chore: add new-module submodule"
git push origin main
```
### Remove a Submodule
```bash
git submodule deinit -f path/to/module
rm -rf .git/modules/path/to/module
git rm -f path/to/module
git commit -m "chore: remove module submodule"
```
---
## 📋 Module Dependency Map
```
falah-os (Core)
├── falah-os-sdk (SDK)
│ ├── falah-os-app (App)
│ ├── falah-os-admin (Admin)
│ └── falah-os-istore (Store)
├── falah-os-sdk-py (Python SDK)
├── falah-os-ummahid (Identity)
└── falah-os-ramz (Protocol)
Portals & Interfaces
├── ulp-portal (User Portal)
├── falah-os-dev-porta (Dev Portal)
├── falah-os-landing (Landing)
└── falah-os-admin (Admin Portal)
Testing & Infrastructure
├── falah-os-mocknet (Mock Network)
└── alah-os-mocknet (Alt Mock Network)
```
---
## 🤝 Contributing Guidelines
### Before Starting
1. Check existing issues and PRs
2. Discuss major changes in issues first
3. Create branch from appropriate base (`develop` for features)
4. Keep commits atomic and well-documented
### During Development
1. Write clean, well-commented code
2. Follow project code style
3. Add/update tests for changes
4. Update documentation
5. Keep branch up-to-date with base branch
### Before Creating PR
1. Run all tests locally
2. Update CHANGELOG.md
3. Verify no conflicts
4. Add meaningful PR description
5. Link to relevant issues
---
## 📚 Additional Resources
- **Main Repository**: https://github.com/Falah-Consultancy-Limited/Falah-OS-Master
- **GitHub Issues**: Report bugs and request features
- **Project Board**: Track development progress
- **Wiki**: Additional documentation (if available)
---
## ✅ Checklist for New Team Members
- [ ] Cloned repository with all submodules
- [ ] Set up local development environment
- [ ] Reviewed this README
- [ ] Reviewed CONTRIBUTING.md in each module
- [ ] Understood branching strategy
- [ ] Set up IDE/editor with project standards
- [ ] Ran tests successfully
- [ ] Created first feature branch
- [ ] Reviewed commit message standards
- [ ] Understood release workflow
---
## 📞 Support & Contact
For questions or issues:
1. **GitHub Issues**: Create an issue in the relevant module repository
2. **Discussions**: Use GitHub Discussions for general questions
3. **Team Communication**: Reach out to the team lead
---
## 📄 License
This project and all submodules are licensed under the terms specified in each module's LICENSE file.
---
**Last Updated**: 2026-05-07
**Maintained By**: Falah Consultancy Limited
**Repository**: https://github.com/Falah-Consultancy-Limited/Falah-OS-Master
+68
View File
@@ -0,0 +1,68 @@
# Falah OS — Roadmap
**Deployment target:** Docker Compose + Komodo (solo-operator)
**Last updated:** April 13, 2026
---
## v1.3 — Current release
**Theme: Production readiness foundation**
### Completed
- [x] Netlify serverless API (auth, screening, certificates, products, finance, revocations)
- [x] Docker Compose stack — Ummah ID, Wallet, RAMZ, Mock-Net, nginx gateway
- [x] Healthchecks on all containers
- [x] Komodo deployment config (`komodo.toml`)
- [x] Makefile with lifecycle commands
- [x] Postgres init.sql + data layer provisioned (reserved for v1.4)
- [x] Rate limiting on nginx gateway
- [x] `.env.example` with all required vars documented
- [x] Hardcoded secrets removed — required vars fail loudly at startup
- [x] Documentation system — end-user, admin, developer, wiki, changelog
- [x] QA checklist and release checklist
### Known gaps (accepted for v1.3)
- No database persistence (in-memory only — see [Limitations](docs/wiki/limitations.md))
- No automated tests
- Frontend source not in repository
- CORS not restricted by origin on Docker services
---
## v1.4 — Next release
**Theme: Persistence + reliability**
- [ ] Wire Postgres to all 4 core services (ummahid, wallet, ramz, mocknet)
- [ ] Redis session caching
- [ ] User management API (create, update, deactivate accounts)
- [ ] Automated backup schedule via cron
- [ ] Basic unit tests for Netlify functions (Vitest)
- [ ] Basic unit tests for Docker services (Jest + supertest)
- [ ] CORS restricted to configured allowed origins
- [ ] Structured JSON logging with request IDs
---
## v1.5 — Future
**Theme: Developer experience**
- [ ] Frontend source code in repository (React + Vite + Tailwind)
- [ ] API versioning and formal deprecation policy
- [ ] Webhook system for external integrations
- [ ] Expanded Shariah contract templates
- [ ] SDK starter kits (JavaScript)
---
## Not in scope
These were in the previous roadmap. They are out of scope for a solo-operator product:
- Kubernetes / Istio / service mesh
- HashiCorp Vault (use OS secrets or Komodo secret store)
- Multi-region DR / chaos engineering
- Horizontal scaling beyond single Docker Compose host
- Mobile native SDKs (v2.x at earliest)
+148
View File
@@ -0,0 +1,148 @@
# Falah OS v1.3 Production - Implementation Tasks
## Phase 1: Core Infrastructure (Weeks 1-2)
### Week 1: Database & Redis HA
- [ ] Create `docker-compose.ha.yml` with PostgreSQL replicas
- [ ] Set up streaming replication configuration
- [ ] Configure pgBouncer for connection pooling
- [ ] Set up Redis Sentinel for automatic failover
- [ ] Configure Redis persistence (AOF + RDB)
- [ ] Create backup script with S3/GCS upload
- [ ] Set up automated daily backup schedule (cron)
- [ ] Test failover scenarios
### Week 2: Load Balancing & Service Mesh
- [ ] Install Traefik/HAProxy
- [ ] Configure SSL termination
- [ ] Set up health checks for all services
- [ ] Configure upstream routing
- [ ] (Optional) Install Istio
- [ ] Configure mTLS between services
- [ ] Update docker-compose.yml with new infrastructure
## Phase 2: Observability (Weeks 2-3)
### Week 2: Metrics & Logging
- [ ] Deploy Prometheus
- [ ] Create service exporters (Node, PostgreSQL, Redis)
- [ ] Build Grafana dashboards (system, API, business)
- [ ] Deploy Loki for log aggregation
- [ ] Implement trace ID propagation
- [ ] Configure log retention policies
- [ ] Set up error alerting
### Week 3: Alerting & Tracing
- [ ] Configure Prometheus Alertmanager
- [ ] Set up notification channels (email, Slack)
- [ ] Implement on-call rotation
- [ ] Deploy OpenTelemetry collectors
- [ ] Set up Jaeger for tracing
- [ ] Document runbooks for common alerts
## Phase 3: Security (Weeks 3-4)
### Week 3: Secrets & Network
- [ ] Deploy HashiCorp Vault
- [ ] Configure dynamic database credentials
- [ ] Migrate all secrets to Vault
- [ ] Implement volume encryption
- [ ] Deploy WAF (ModSecurity/Cloudflare)
- [ ] Configure network policies
### Week 4: Audit & Compliance
- [ ] Implement immutable audit logging
- [ ] Create transaction audit trail
- [ ] Run dependency vulnerability scan (Trivy)
- [ ] Scan all Docker images for CVEs
- [ ] Conduct penetration testing
- [ ] Document compliance controls
## Phase 4: Reliability (Weeks 4-5)
### Week 4: Disaster Recovery
- [ ] Design multi-region architecture
- [ ] Create DR automation scripts
- [ ] Document RTO/RPO procedures
- [ ] Test failover to DR region
- [ ] Verify backup restoration
### Week 5: Chaos & Resilience
- [ ] Deploy Chaos Mesh
- [ ] Create failure injection scenarios
- [ ] Implement circuit breakers
- [ ] Add retry with exponential backoff
- [ ] Set up dead letter queues
- [ ] Run first Game Day exercise
## Phase 5: Performance (Weeks 5-6)
### Week 5: Caching & Optimization
- [ ] Integrate CDN (Cloudflare/Fastly)
- [ ] Implement Redis API caching
- [ ] Enable HTTP/2 on nginx
- [ ] Configure response compression
- [ ] Optimize PostgreSQL queries
- [ ] Add indexes for common queries
### Week 6: Load Testing & Launch
- [ ] Write k6 load test scenarios
- [ ] Run baseline performance tests
- [ ] Conduct stress testing
- [ ] Define performance budgets
- [ ] Finalize SLA documentation
- [ ] Production go-live
---
## Documentation Tasks
- [ ] Update architecture diagrams
- [ ] Create deployment runbooks
- [ ] Document API references
- [ ] Write security policies
- [ ] Create user guides
## Testing Tasks
- [ ] Unit tests for all services
- [ ] Integration tests for API Gateway
- [ ] E2E tests for critical paths
- [ ] Performance benchmarks
- [ ] Security penetration tests
---
## New Features (If Time Permits)
- [ ] Multi-tenant isolation
- [ ] API versioning implementation
- [ ] Webhook system
- [ ] Plugin system design
- [ ] Mobile SDKs (iOS/Android)
---
## Dependencies
### External Services Needed
- S3/GCS bucket for backups
- CDN provider (Cloudflare/Fastly)
- Monitoring domain
- SSL certificates (Let's Encrypt)
- Email service for alerts (SendGrid/SES)
- PagerDuty/OpsGenie for on-call
### Tools Required
- k6 (load testing)
- Trivy (container scanning)
- Chaos Mesh (chaos engineering)
- Vault (secrets management)
- Prometheus/Grafana (monitoring)
- Loki (logging)
- Jaeger (tracing)
---
## Notes
- All critical (P0) tasks must complete before production launch
- P1 tasks should complete within 2 weeks of launch
- P2 tasks are optional for v1.3
- Review progress weekly with stakeholders
+148
View File
@@ -0,0 +1,148 @@
# Falah OS CE — Optional Community Extras
# Heavy open-source tools for advanced communities.
#
# Usage (add on top of the main stack):
# docker compose -f docker-compose.yml -f docker-compose.extras.yml up -d
#
# Or enable individual services:
# docker compose -f docker-compose.yml -f docker-compose.extras.yml up -d rocketchat mattermost-db mattermost
#
# Requirements: additional 4 GB RAM recommended.
services:
# ---------------------------------------------------------------------------
# Rocket.Chat — Full-featured community messaging
# Access: http://<host>:3100
# Replaces the built-in chat service for large communities
# ---------------------------------------------------------------------------
rocketchat:
image: rocket.chat:6
container_name: falah-rocketchat
ports:
- "3100:3000"
environment:
- MONGO_URL=mongodb://mongo-rc:27017/rocketchat
- MONGO_OPLOG_URL=mongodb://mongo-rc:27017/local
- ROOT_URL=http://localhost:3100
- PORT=3000
- DEPLOY_PLATFORM=docker
depends_on:
- mongo-rc
networks:
- falah-network
restart: unless-stopped
mongo-rc:
image: mongo:6
container_name: falah-mongo-rc
command: mongod --oplogSize 128 --replSet rs0
volumes:
- mongo-rc-data:/data/db
networks:
- falah-network
restart: unless-stopped
mongo-rc-init:
image: mongo:6
container_name: falah-mongo-rc-init
command: >
bash -c "sleep 10 && mongosh --host mongo-rc --eval 'rs.initiate({_id: \"rs0\", members: [{_id: 0, host: \"mongo-rc:27017\"}]})'"
depends_on:
- mongo-rc
networks:
- falah-network
restart: on-failure
# ---------------------------------------------------------------------------
# Moodle — Learning Management System
# Access: http://<host>:3200
# For madrasah, Islamic college, and structured course delivery
# ---------------------------------------------------------------------------
moodle:
image: bitnami/moodle:latest
container_name: falah-moodle
ports:
- "3200:8080"
environment:
- MOODLE_DATABASE_HOST=moodle-db
- MOODLE_DATABASE_PORT_NUMBER=3306
- MOODLE_DATABASE_NAME=moodle
- MOODLE_DATABASE_USER=moodle
- MOODLE_DATABASE_PASSWORD=${POSTGRES_PASSWORD}
- MOODLE_USERNAME=admin
- MOODLE_PASSWORD=${ADMIN_SECRET}
- MOODLE_EMAIL=admin@falah.os
- MOODLE_SITE_NAME=Falah OS Learning
depends_on:
- moodle-db
volumes:
- moodle-data:/bitnami/moodle
- moodle-data-dir:/bitnami/moodledata
networks:
- falah-network
restart: unless-stopped
moodle-db:
image: mariadb:10.6
container_name: falah-moodle-db
environment:
- MARIADB_ROOT_PASSWORD=${POSTGRES_PASSWORD}
- MARIADB_DATABASE=moodle
- MARIADB_USER=moodle
- MARIADB_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- moodle-db-data:/var/lib/mysql
networks:
- falah-network
restart: unless-stopped
# ---------------------------------------------------------------------------
# Nextcloud — File sharing, calendar, contacts, collaborative docs
# Access: http://<host>:3300
# For neighbourhood committees, college research, document sharing
# ---------------------------------------------------------------------------
nextcloud:
image: nextcloud:28-apache
container_name: falah-nextcloud
ports:
- "3300:80"
environment:
- POSTGRES_HOST=nextcloud-db
- POSTGRES_DB=nextcloud
- POSTGRES_USER=nextcloud
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- NEXTCLOUD_ADMIN_USER=admin
- NEXTCLOUD_ADMIN_PASSWORD=${ADMIN_SECRET}
- NEXTCLOUD_TRUSTED_DOMAINS=localhost 192.168.0.17
depends_on:
- nextcloud-db
volumes:
- nextcloud-data:/var/www/html
networks:
- falah-network
restart: unless-stopped
nextcloud-db:
image: postgres:16-alpine
container_name: falah-nextcloud-db
environment:
- POSTGRES_DB=nextcloud
- POSTGRES_USER=nextcloud
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- nextcloud-db-data:/var/lib/postgresql/data
networks:
- falah-network
restart: unless-stopped
volumes:
mongo-rc-data:
moodle-data:
moodle-data-dir:
moodle-db-data:
nextcloud-data:
nextcloud-db-data:
+330
View File
@@ -0,0 +1,330 @@
# Falah OS v1.3 - High Availability Infrastructure
# Usage: docker-compose -f docker-compose.yml -f docker-compose.ha.yml up -d
version: '3.8'
services:
# =============================================================================
# HIGH AVAILABILITY DATABASE
# =============================================================================
postgres-primary:
image: postgres:16-alpine
container_name: falah-postgres-primary
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=falahsecure
- POSTGRES_DB=falahdb
- POSTGRES_HOST_AUTH_METHOD=trust
volumes:
- postgres-primary-data:/var/lib/postgresql/data
- ./infrastructure/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- falah-network
deploy:
resources:
limits:
cpus: '2.0'
memory: 4G
reservations:
cpus: '1.0'
memory: 2G
command: >
postgres
-c max_connections=300
-c shared_buffers=2GB
-c effective_cache_size=6GB
-c maintenance_work_mem=512MB
-c checkpoint_completion_target=0.9
-c wal_level=replica
-c max_wal_senders=10
-c wal_keep_size=1GB
-c hot_standby=on
postgres-replica-1:
image: postgres:16-alpine
container_name: falah-postgres-replica-1
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=falahsecure
- POSTGRES_DB=falahdb
volumes:
- postgres-replica-1-data:/var/lib/postgresql/data
networks:
- falah-network
depends_on:
- postgres-primary
deploy:
resources:
limits:
cpus: '2.0'
memory: 4G
command: >
postgres
-c hot_standby=on
-c hot_standby_feedback=on
-c max_standby_streaming_delay=30s
postgres-replica-2:
image: postgres:16-alpine
container_name: falah-postgres-replica-2
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=falahsecure
- POSTGRES_DB=falahdb
volumes:
- postgres-replica-2-data:/var/lib/postgresql/data
networks:
- falah-network
depends_on:
- postgres-primary
deploy:
resources:
limits:
cpus: '2.0'
memory: 4G
command: >
postgres
-c hot_standby=on
-c hot_standby_feedback=on
-c max_standby_streaming_delay=30s
# =============================================================================
# REDIS HIGH AVAILABILITY (Sentinel)
# =============================================================================
redis-master:
image: redis:7-alpine
container_name: falah-redis-master
command: >
redis-server
--appendonly yes
--maxmemory 2gb
--maxmemory-policy allkeys-lru
--requirepass falahsecure
volumes:
- redis-master-data:/data
networks:
- falah-network
deploy:
resources:
limits:
cpus: '1.0'
memory: 2G
redis-replica-1:
image: redis:7-alpine
container_name: falah-redis-replica-1
command: >
redis-server
--appendonly yes
--replicaof falah-redis-master 6379
--masterauth falahsecure
--requirepass falahsecure
volumes:
- redis-replica-1-data:/data
networks:
- falah-network
depends_on:
- redis-master
deploy:
resources:
limits:
cpus: '1.0'
memory: 2G
redis-replica-2:
image: redis:7-alpine
container_name: falah-redis-replica-2
command: >
redis-server
--appendonly yes
--replicaof falah-redis-master 6379
--masterauth falahsecure
--requirepass falahsecure
volumes:
- redis-replica-2-data:/data
networks:
- falah-network
depends_on:
- redis-master
deploy:
resources:
limits:
cpus: '1.0'
memory: 2G
redis-sentinel:
image: redis:7-alpine
container_name: falah-redis-sentinel
command: >
redis-sentinel
--sentinel monitor falah-master falah-redis-master 6379 2
--sentinel down-after-milliseconds falah-master 5000
--sentinel failover-timeout falah-master 60000
--sentinel parallel-syncs falah-master 1
volumes:
- ./infrastructure/redis/sentinel.conf:/etc/redis/sentinel.conf
networks:
- falah-network
depends_on:
- redis-master
- redis-replica-1
- redis-replica-2
# =============================================================================
# CONNECTION POOLING
# =============================================================================
pgbouncer:
image: pgbouncer/pgbouncer:latest
container_name: falah-pgbouncer
environment:
- DATABASE_URL=postgresql://postgres:falahsecure@falah-postgres-primary:5432/falahdb
- POOL_MODE=transaction
- MAX_CLIENT_conn=500
- DEFAULT_POOL_SIZE=50
- MIN_POOL_SIZE=10
volumes:
- ./infrastructure/pgbouncer/pgbouncer.ini:/etc/pgbouncer/pgbouncer.ini
- ./infrastructure/pgbouncer/userlist.txt:/etc/pgbouncer/userlist.txt
ports:
- "6432:5432"
networks:
- falah-network
depends_on:
- postgres-primary
# =============================================================================
# LOAD BALANCER (Traefik)
# =============================================================================
traefik:
image: traefik:v3.0
container_name: falah-traefik
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.email=admin@falah-os.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--log.level=INFO"
- "--accesslog=true"
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./infrastructure/traefik/traefik.yml:/etc/traefik/traefik.yml:ro
- ./infrastructure/traefik/acme.json:/letsencrypt/acme.json
networks:
- falah-network
restart: unless-stopped
# =============================================================================
# OBSERVABILITY STACK
# =============================================================================
prometheus:
image: prom/prometheus:latest
container_name: falah-prometheus
volumes:
- ./infrastructure/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=30d'
ports:
- "9090:9090"
networks:
- falah-network
restart: unless-stopped
grafana:
image: grafana/grafana:latest
container_name: falah-grafana
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD:-changeme}
- GF_INSTALL_PLUGINS=grafana-piechart-panel
volumes:
- grafana-data:/var/lib/grafana
- ./infrastructure/grafana/dashboards:/etc/grafana/provisioning/dashboards:ro
- ./infrastructure/grafana/datasources:/etc/grafana/provisioning/datasources:ro
ports:
- "3000:3000"
networks:
- falah-network
depends_on:
- prometheus
restart: unless-stopped
loki:
image: grafana/loki:latest
container_name: falah-loki
volumes:
- ./infrastructure/loki/loki-config.yml:/etc/loki/local-config.yaml:ro
- loki-data:/loki
ports:
- "3100:3100"
networks:
- falah-network
restart: unless-stopped
promtail:
image: grafana/promtail:latest
container_name: falah-promtail
volumes:
- ./infrastructure/promtail/promtail-config.yml:/etc/promtail/config.yml:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
command:
- "-config.file=/etc/promtail/config.yml"
networks:
- falah-network
restart: unless-stopped
alertmanager:
image: prom/alertmanager:latest
container_name: falah-alertmanager
volumes:
- ./infrastructure/alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
- alertmanager-data:/alertmanager
ports:
- "9093:9093"
networks:
- falah-network
restart: unless-stopped
volumes:
postgres-primary-data:
driver: local
postgres-replica-1-data:
driver: local
postgres-replica-2-data:
driver: local
redis-master-data:
driver: local
redis-replica-1-data:
driver: local
redis-replica-2-data:
driver: local
prometheus-data:
driver: local
grafana-data:
driver: local
loki-data:
driver: local
alertmanager-data:
driver: local
networks:
falah-network:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/16
+203
View File
@@ -0,0 +1,203 @@
# Local Development with Docker
# Usage: docker-compose -f docker-compose.local.yml up -d
version: '3.8'
services:
# =============================================================================
# CORE LAYER - Falah OS Services
# =============================================================================
# Ummah ID - Sovereign Identity Service
ummahid:
build:
context: ./docker/services/ummahid
dockerfile: Dockerfile
container_name: falah-ummahid
ports:
- "3001:3000"
environment:
- NODE_ENV=development
- SERVICE_NAME=ummahid
- REDIS_URL=redis://redis:6379
- DATABASE_URL=postgresql://postgres:falahdev@postgres:5432/falahid
- JWT_SECRET=dev_jwt_secret_change_in_production
- ENCRYPTION_KEY=dev_encryption_key_change_in_production
depends_on:
- postgres
- redis
networks:
- falah-dev-network
restart: unless-stopped
# Wallet Service - FLH Currency Management
wallet:
build:
context: ./docker/services/wallet
dockerfile: Dockerfile
container_name: falah-wallet
ports:
- "3002:3000"
environment:
- NODE_ENV=development
- SERVICE_NAME=wallet
- REDIS_URL=redis://redis:6379
- DATABASE_URL=postgresql://postgres:falahdev@postgres:5432/falahwallet
- MOCKNET_URL=http://mocknet:3000
- PROTOCOL_FEE_PERCENT=1.5
depends_on:
- postgres
- redis
- ummahid
networks:
- falah-dev-network
restart: unless-stopped
# RAMZ Contract Engine - Shariah-Compliant Smart Contracts
ramz:
build:
context: ./docker/services/ramz
dockerfile: Dockerfile
container_name: falah-ramz
ports:
- "3003:3000"
environment:
- NODE_ENV=development
- SERVICE_NAME=ramz
- REDIS_URL=redis://redis:6379
- DATABASE_URL=postgresql://postgres:falahdev@postgres:5432/falahramz
- SHARIAH_RULES=NO_RIBA,NO_GHARAR,NO_MAYSIR,HALAL_COMMODITY,ASSET_BACKED,MUTUAL_CONSENT,NO_BAI_INAH
depends_on:
- postgres
- redis
- ummahid
- wallet
networks:
- falah-dev-network
restart: unless-stopped
# Mock-Net Sandbox - Digital Twin Environment
mocknet:
build:
context: ./docker/services/mocknet
dockerfile: Dockerfile
container_name: falah-mocknet
ports:
- "3004:3000"
environment:
- NODE_ENV=development
- SERVICE_NAME=mocknet
- REDIS_URL=redis://redis:6379
- DATABASE_URL=postgresql://postgres:falahdev@postgres:5432/falahmock
- ENABLE_CHAOS_TESTING=true
depends_on:
- postgres
- redis
networks:
- falah-dev-network
restart: unless-stopped
# =============================================================================
# API GATEWAY
# =============================================================================
api-gateway:
build:
context: ./docker/services/api-gateway
dockerfile: Dockerfile
container_name: falah-api-gateway
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- SERVICE_NAME=api-gateway
- REDIS_URL=redis://redis:6379
- UMMAHID_URL=http://ummahid:3000
- WALLET_URL=http://wallet:3000
- RAMZ_URL=http://ramz:3000
- MOCKNET_URL=http://mocknet:3000
depends_on:
- redis
- ummahid
- wallet
- ramz
networks:
- falah-dev-network
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
# =============================================================================
# DATA LAYER
# =============================================================================
postgres:
image: postgres:16-alpine
container_name: falah-postgres-dev
ports:
- "5432:5432"
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=falahdev
- POSTGRES_DB=falahdb
volumes:
- postgres-dev-data:/var/lib/postgresql/data
- ./docker/infrastructure/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- falah-dev-network
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: falah-redis-dev
ports:
- "6379:6379"
volumes:
- redis-dev-data:/data
networks:
- falah-dev-network
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
command: redis-server --appendonly yes
# =============================================================================
# FRONTEND (Netlify Dev)
# =============================================================================
# Uncomment for local frontend development
# frontend:
# image: node:20-alpine
# working_dir: /app
# command: sh -c "npm install -g netlify-cli && netlify dev"
# volumes:
# - .:/app
# ports:
# - "8888:8888"
# - "1313:1313"
# networks:
# - falah-dev-network
volumes:
postgres-dev-data:
driver: local
redis-dev-data:
driver: local
networks:
falah-dev-network:
driver: bridge
ipam:
config:
- subnet: 172.30.0.0/16
+38
View File
@@ -0,0 +1,38 @@
import { test, expect } from '@playwright/test';
test.describe('Authentication Flow', () => {
test('should handle login, registration, and logout', async ({ page }) => {
// Visit site → home page loads → see navbar
await page.goto('/');
await expect(page.locator('nav')).toBeVisible();
// Click Login → see login form
await page.click('text=Login');
await expect(page.locator('form')).toBeVisible();
// Try invalid login → see error message
await page.fill('input[type="email"]', 'invalid@example.com');
await page.fill('input[type="password"]', 'wrongpassword');
await page.click('button[type="submit"]');
await expect(page.locator('text=Invalid credentials')).toBeVisible();
// Navigate to registration (assuming a link exists on login page)
await page.click('text=Register');
// Fill registration form → submit → redirect to home
await page.fill('input[name="name"]', 'Test User');
await page.fill('input[name="email"]', 'newuser@example.com');
await page.fill('input[name="password"]', 'securepassword123');
await page.click('button[type="submit"]');
// Check redirect and authentication state
await expect(page).toHaveURL('/');
// See user menu or similar in navbar when authenticated
await expect(page.locator('nav')).toContainText('Logout');
// Click logout → redirect to home → see login button
await page.click('text=Logout');
await expect(page).toHaveURL('/');
await expect(page.locator('text=Login')).toBeVisible();
});
});
+137
View File
@@ -0,0 +1,137 @@
# Halal Monitor — Strategic Brief
## Vision
Rebrand the existing World Monitor project as **Halal Monitor** — a premium feature of FalahMobile. A global map-based dashboard for the Muslim ummah to find halal restaurants, mosques, and prayer spaces anywhere in the world.
## Core Features
### Phase 1 — MVP (Map + Data)
| Feature | Description | Data Source | API Cost |
|---|---|---|---|
| **Mosques & Prayer Places** | Map of nearby mosques worldwide with name, address, prayer times | Overpass API (OpenStreetMap) | **Free** |
| **Halal Restaurants** | Halal-certified/muslim-owned restaurants in major cities | Google Places API + OSM tags | Google: ~$7/1K calls (free $200/mo credit) |
| **Search by City** | Search bar + autocomplete for city/mosque/restaurant | Nominatim (OSM geocoder) | **Free** |
| **Current Location** | "Near me" — geolocate user and show nearby places | Browser Geolocation API | **Free** |
### Phase 2 — Premium Enhancements (FalahMobile Premium)
| Feature | Description |
|---|---|
| **Halal Certifications** | Verified halal certification badges per restaurant |
| **User Reviews & Ratings** | Community-driven halal rating system |
| **Bookmarks & Favorites** | Save places, get alerts when new verified places added |
| **Trip Planner** | Plan routes with halal restaurants + prayer stops along the way |
| **Crowd-Sourced Updates** | Premium users can submit new halal places for verification |
### Phase 3 — Platform
| Feature | Description |
|---|---|
| **Weekly Digest** | New halal places in your city, emailed/notified |
| **Monthly Trends** | Most-reviewed, top-rated, newly certified |
| **Ambassador Program** | Community mods who verify new submissions |
## Architecture
### Integration into FalahMobile
```
FalahMobile (Next.js 16 App Router)
├── /halal-monitor → Page (gated: isPremium required)
├── /api/halal/places → Nearby places API (Overpass + Google)
├── /api/halal/bookmarks → User bookmarks CRUD
├── /api/halal/usage → Track API usage/quotas (for free tier)
└── lib/halal-monitor.ts → Shared helpers (geocoding, map utils)
```
### Tech Choices
| Concern | Choice | Why |
|---|---|---|
| **Map renderer** | **Leaflet** (react-leaflet) | Free, no API key, light (~40KB gzip), works with OSM tiles |
| **Map tiles** | OpenStreetMap tile layer | Free tier, no API key, or optional MapTiler ($) for styled maps |
| **Mosque data** | **Overpass API** | OpenStreetMap query language, query `amenity=place_of_worship + religion=muslim` |
| **Halal restaurant data** | OSM tags (`diet:halal=yes`) + Google Places fallback | OSM free but less complete; Google fills gaps |
| **Geocoding** | Nominatim (OSM) | Free, 1 req/sec limit — fine for single-user |
| **Premium gating** | `user.isPremium` in AuthContext | Already exists in Prisma schema |
| **Map markers clustering** | `leaflet.markercluster` | Handles 1000+ markers smoothly |
| **Styling** | TailwindCSS dark theme (existing) | Consistent with FalahMobile design |
### Data Flow
```
User opens /halal-monitor
→ Auth check: user.isPremium? No → Show upgrade CTA
→ Yes → Browser geolocates (or city search)
→ Fetch /api/halal/places?lat=X&lng=Y&radius=5000
→ Server queries Overpass API for mosques + halal restaurants
→ Caches results in SQLite (expire after 24h)
→ Returns GeoJSON
→ Render map with Leaflet + clustered markers
→ Two marker layers: mosques (green) / restaurants (blue)
```
## Premium Gating Strategy
### What's Free vs Premium
| Feature | Free Users | Premium Users |
|---|---|---|
| View map | ✅ | ✅ |
| Search cities | ✅ | ✅ |
| See mosques | ✅ | ✅ |
| See halal restaurants | ❌ | ✅ |
| Bookmarks | ❌ (view-only) | ✅ (save & organize) |
| API calls/day | 20 | Unlimited |
| Crowd-sourced edits | ❌ | ✅ |
| Trip planner | ❌ | ✅ |
### Upgrade CTA Placement
- **Souq page** — small "Upgrade to Premium" badge in sidebar
- **Halal Monitor page** — if not premium, show map with greyed-out restaurant layer + upgrade prompt
- **Navbar** — crown icon next to user name for premium users; "✨ Upgrade" for free users
## Monetization
- **Premium subscription** — unlocks full Halal Monitor + other premium features
- **Price** — TBD (existing isPremium/isPro fields support multiple tiers)
- **Listing fee discount** — premium users pay 5% platform fee instead of 10%
## Implementation Phases
### Phase 1 (build in this session)
1. ✅ Create this strategic brief
2. **Halal Monitor page**`/halal-monitor` with Leaflet map, premium gate
3. **API route**`/api/halal/places` querying Overpass API for mosques
4. **Navbar update** — add Halal Monitor link (with premium badge)
5. **Premium gating**`isPremium` check on page access
6. **Deploy** — rebuild image, push to Synology, update Dockge stack
7. **QA** — test map rendering, mosque search, premium gate
### Phase 2 (next session)
1. Halal restaurants layer (Google Places API)
2. Bookmarks CRUD
3. Crowd-sourced submission form
4. Trip planner
### Phase 3 (future)
1. Community review system
2. Weekly digests
3. Ambassador verification program
## Data & Privacy
- No user location data stored server-side — geolocation is ephemeral (browser)
- Bookmark data stored in SQLite (existing FalahMobile DB)
- Overpass API calls are anonymous
- Rate limiting: 60 req/min per user on free tier (enforced server-side)
## Key Risks & Mitigations
| Risk | Mitigation |
|---|---|
| OSM data incomplete for halal restaurants | Add Google Places API fallback + crowd-sourced submissions |
| Overpass API rate limits (1 req/sec) | Server-side caching (24h TTL), batch queries |
| Free users burning API quota | Hard cap at 20 req/day for free, tracked via usage table |
| iCloud datalock on World Monitor source | Build fresh from OSM + Google APIs — don't depend on old code |
| Premium adoption low | Cross-sell on Souq page, make Halal Monitor a visible premium showcase |
---
*Last updated: June 8, 2026*
*Status: Strategic Brief — Ready for Phase 1 implementation*
Executable
+240
View File
@@ -0,0 +1,240 @@
#!/usr/bin/env bash
# =============================================================================
# Falah OS — One-line installer
#
# curl -fsSL https://raw.githubusercontent.com/falah-consultancy-limited/falah-os-master/main/install.sh | bash
# — or —
# wget -qO- https://raw.githubusercontent.com/falah-consultancy-limited/falah-os-master/main/install.sh | bash
#
# What it does:
# 1. Checks prerequisites (git, docker, docker compose, openssl)
# 2. Clones the repo (or pulls if already cloned)
# 3. Generates all required secrets
# 4. Writes .env (skips any var already set by the environment)
# 5. Starts the stack with docker compose
# 6. Runs a health check
# =============================================================================
set -euo pipefail
# ---------------------------------------------------------------------------
# Colours & helpers
# ---------------------------------------------------------------------------
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BOLD='\033[1m'
NC='\033[0m'
info() { echo -e "${BLUE}[falah]${NC} $*"; }
success() { echo -e "${GREEN}[falah]${NC} $*"; }
warn() { echo -e "${YELLOW}[falah]${NC} $*"; }
fatal() { echo -e "${RED}[falah] ERROR:${NC} $*" >&2; exit 1; }
# ---------------------------------------------------------------------------
# Banner
# ---------------------------------------------------------------------------
echo ""
echo -e "${BOLD}${GREEN}╔═══════════════════════════════════════╗${NC}"
echo -e "${BOLD}${GREEN}║ Falah OS — Installer v1.3 ║${NC}"
echo -e "${BOLD}${GREEN}║ Shariah-compliant digital economy ║${NC}"
echo -e "${BOLD}${GREEN}╚═══════════════════════════════════════╝${NC}"
echo ""
# ---------------------------------------------------------------------------
# Prerequisite checks
# ---------------------------------------------------------------------------
info "Checking prerequisites..."
check_cmd() {
command -v "$1" >/dev/null 2>&1 || fatal "$1 is required but not installed. $2"
}
check_cmd git "Install via your package manager (apt/brew/dnf)."
check_cmd docker "Install Docker: https://docs.docker.com/get-docker/"
check_cmd openssl "Install via your package manager (apt/brew/dnf)."
# Docker Compose v2 (plugin) or v1 (standalone)
if docker compose version >/dev/null 2>&1; then
COMPOSE="docker compose"
elif command -v docker-compose >/dev/null 2>&1; then
COMPOSE="docker-compose"
else
fatal "Docker Compose is required. Install: https://docs.docker.com/compose/install/"
fi
# Docker daemon running?
docker info >/dev/null 2>&1 || fatal "Docker daemon is not running. Start Docker and try again."
success "Prerequisites OK"
echo ""
# ---------------------------------------------------------------------------
# Clone or update
# ---------------------------------------------------------------------------
REPO_URL="https://github.com/falah-consultancy-limited/falah-os-master.git"
INSTALL_DIR="${FALAH_INSTALL_DIR:-$HOME/falah-os}"
if [ -d "$INSTALL_DIR/.git" ]; then
info "Found existing install at $INSTALL_DIR — pulling latest..."
git -C "$INSTALL_DIR" pull --ff-only origin main
else
info "Cloning Falah OS into $INSTALL_DIR..."
git clone --depth 1 "$REPO_URL" "$INSTALL_DIR"
fi
cd "$INSTALL_DIR"
success "Codebase ready at $INSTALL_DIR"
echo ""
# ---------------------------------------------------------------------------
# Generate secrets
# ---------------------------------------------------------------------------
gen_secret() {
openssl rand -base64 48 | tr -d '=+/' | cut -c1-48
}
info "Generating secrets..."
# Respect any secrets already exported in the environment
JWT_SECRET="${JWT_SECRET:-$(gen_secret)}"
ENCRYPTION_KEY="${ENCRYPTION_KEY:-$(gen_secret)}"
ADMIN_SECRET="${ADMIN_SECRET:-$(gen_secret)}"
POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-$(gen_secret)}"
REDIS_PASSWORD="${REDIS_PASSWORD:-$(gen_secret)}"
success "Secrets generated"
echo ""
# ---------------------------------------------------------------------------
# Write .env
# ---------------------------------------------------------------------------
if [ -f .env ]; then
warn ".env already exists — merging generated secrets for any blank values..."
# For each required var, only fill in if currently blank/missing in .env
fill_if_blank() {
local var="$1" val="$2"
if grep -qE "^${var}=\s*$" .env 2>/dev/null || ! grep -qE "^${var}=" .env 2>/dev/null; then
# Escape forward slashes in value for sed
local escaped_val
escaped_val=$(printf '%s\n' "$val" | sed 's/[\/&]/\\&/g')
if grep -qE "^${var}=" .env; then
sed -i "s|^${var}=.*|${var}=${escaped_val}|" .env
else
echo "${var}=${val}" >> .env
fi
fi
}
fill_if_blank JWT_SECRET "$JWT_SECRET"
fill_if_blank ENCRYPTION_KEY "$ENCRYPTION_KEY"
fill_if_blank ADMIN_SECRET "$ADMIN_SECRET"
fill_if_blank POSTGRES_PASSWORD "$POSTGRES_PASSWORD"
fill_if_blank REDIS_PASSWORD "$REDIS_PASSWORD"
else
info "Writing .env..."
cp .env.example .env
# Replace blank values for required vars
sed -i "s|^JWT_SECRET=.*|JWT_SECRET=${JWT_SECRET}|" .env
sed -i "s|^ENCRYPTION_KEY=.*|ENCRYPTION_KEY=${ENCRYPTION_KEY}|" .env
sed -i "s|^ADMIN_SECRET=.*|ADMIN_SECRET=${ADMIN_SECRET}|" .env
sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=${POSTGRES_PASSWORD}|" .env
sed -i "s|^REDIS_PASSWORD=.*|REDIS_PASSWORD=${REDIS_PASSWORD}|" .env
fi
success ".env configured"
echo ""
# ---------------------------------------------------------------------------
# Verify .env before launch
# ---------------------------------------------------------------------------
info "Verifying environment..."
missing=0
for var in JWT_SECRET ENCRYPTION_KEY ADMIN_SECRET POSTGRES_PASSWORD REDIS_PASSWORD; do
val=$(grep -E "^${var}=.+" .env 2>/dev/null | cut -d= -f2- || true)
if [ -z "$val" ]; then
warn "Still missing: $var"
missing=1
fi
done
[ "$missing" -eq 0 ] || fatal "Cannot start with missing required env vars. Edit .env and re-run."
success "Environment OK"
echo ""
# ---------------------------------------------------------------------------
# Start the stack
# ---------------------------------------------------------------------------
info "Starting Falah OS stack..."
$COMPOSE pull --quiet
$COMPOSE up -d
echo ""
info "Waiting for services to become ready..."
sleep 10
# ---------------------------------------------------------------------------
# Health check (inline — no dependency on scripts/health-check.sh)
# ---------------------------------------------------------------------------
PASS=0
FAIL=0
check_http() {
local name="$1" url="$2"
printf " %-20s" "$name"
if curl -sf --max-time 5 "$url" >/dev/null 2>&1; then
echo -e "${GREEN}healthy${NC}"
PASS=$((PASS + 1))
else
echo -e "${RED}not responding${NC}"
FAIL=$((FAIL + 1))
fi
}
echo -e "${BLUE}Service health:${NC}"
check_http "API Gateway" "http://localhost:3000/health"
check_http "Ummah ID" "http://localhost:3001/health"
check_http "Wallet" "http://localhost:3002/health"
check_http "RAMZ" "http://localhost:3003/health"
check_http "Mock-Net" "http://localhost:3004/health"
check_http "App" "http://localhost:3005/health"
check_http "falahd" "http://localhost:3006/health"
echo ""
# ---------------------------------------------------------------------------
# Done
# ---------------------------------------------------------------------------
if [ "$FAIL" -eq 0 ]; then
echo -e "${BOLD}${GREEN}╔═══════════════════════════════════════╗${NC}"
echo -e "${BOLD}${GREEN}║ Falah OS is running! ║${NC}"
echo -e "${BOLD}${GREEN}╚═══════════════════════════════════════╝${NC}"
echo ""
echo -e " Dashboard: ${BOLD}http://localhost:3005${NC}"
echo -e " API: ${BOLD}http://localhost:3000${NC}"
echo -e " Admin key: stored in ${BOLD}${INSTALL_DIR}/.env${NC}"
echo ""
echo -e " Useful commands:"
echo -e " cd ${INSTALL_DIR}"
echo -e " make logs # tail all service logs"
echo -e " make health # re-run health checks"
echo -e " make down # stop the stack"
echo ""
else
warn "${FAIL} service(s) did not respond. Check logs:"
echo ""
echo -e " cd ${INSTALL_DIR} && make logs"
echo ""
echo -e " The stack is running — services may still be starting up."
echo -e " Re-run health check in ~30 s: ${BOLD}make health${NC}"
fi
+38
View File
@@ -0,0 +1,38 @@
## Falah OS v1.3 — Komodo Deployment Config
## https://komo.do/docs/resources/stack
##
## This file defines the Falah OS stack for Komodo.
## Import via: Komodo UI → Stacks → Import → paste this file.
[[stack]]
name = "falah-os"
description = "Falah OS v1.3 — Halal Token Engine + iBaaS EE Gateway"
## Point at your repo and the compose file within it
[stack.config]
repo = "https://github.com/Falah-Consultancy-Limited/Falah-OS-Master.git"
branch = "main"
compose_file = "docker-compose.yml"
## Environment — set these in Komodo's secret store, not here
## Komodo UI → Variables → New Variable (mark as Secret)
##
## Required secrets to configure:
## JWT_SECRET — openssl rand -base64 32
## ENCRYPTION_KEY — openssl rand -base64 32
## ADMIN_SECRET — openssl rand -base64 32
## POSTGRES_PASSWORD — strong password
## REDIS_PASSWORD — strong password
##
## Optional:
## PROTOCOL_FEE_PERCENT (default: 1.5)
## SHARIAH_RULES (default: NO_RIBA,NO_GHARAR,...)
## ENABLE_CHAOS_TESTING (default: false)
## Webhook — enable auto-deploy on git push
[stack.config.webhook]
enabled = true
branch = "main"
## Alert on stack events (configure in Komodo UI)
## Komodo UI → Alerters → connect Slack / email as needed
+15
View File
@@ -0,0 +1,15 @@
[build]
command = "npm run build"
publish = ".next"
[[plugins]]
package = "@netlify/plugin-nextjs"
[context.production]
environment = { NODE_VERSION = "22" }
[context.deploy-preview]
environment = { NODE_VERSION = "22" }
[context.branch-deploy]
environment = { NODE_VERSION = "22" }
+1 -1
View File
@@ -1,7 +1,7 @@
import type { NextConfig } from "next"; import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
output: "standalone", output: process.env.NETLIFY === "true" ? undefined : "standalone",
}; };
export default nextConfig; export default nextConfig;
+26
View File
@@ -0,0 +1,26 @@
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: 2,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
video: 'retain-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});
+20
View File
@@ -0,0 +1,20 @@
# Falah OS v1.3 — Portainer Stack Environment Variables
# Paste these into Portainer → Stacks → (your stack) → Environment variables
# Generate secrets with: openssl rand -base64 48 | tr -d '=+/' | cut -c1-48
#
# REQUIRED — stack will not start without these
# ─────────────────────────────────────────────
JWT_SECRET=
ENCRYPTION_KEY=
ADMIN_SECRET=
POSTGRES_PASSWORD=
REDIS_PASSWORD=
# OPTIONAL — safe defaults shown
# ────────────────────────────────
POSTGRES_USER=postgres
PROTOCOL_FEE_PERCENT=1.5
SHARIAH_RULES=NO_RIBA,NO_GHARAR,NO_MAYSIR,HALAL_COMMODITY,ASSET_BACKED,MUTUAL_CONSENT,NO_BAI_INAH
ENABLE_CHAOS_TESTING=false
+41
View File
@@ -0,0 +1,41 @@
import { render, screen } from '@testing-library/react'
import { describe, it, expect, vi } from 'vitest'
// We create a mock Navbar component since the real one might be complex
// and we just want to demonstrate a sample test
const Navbar = ({ isAuthenticated, user }: { isAuthenticated: boolean; user?: { name: string } }) => {
return (
<nav>
<div>FalahMobile</div>
{isAuthenticated ? (
<div>
<span>Welcome, {user?.name}</span>
<button>Logout</button>
</div>
) : (
<div>
<button>Login</button>
<button>Sign Up</button>
</div>
)}
</nav>
)
}
describe('Navbar Component', () => {
it('should show login and signup when unauthenticated', () => {
render(<Navbar isAuthenticated={false} />)
expect(screen.getByText('Login')).toBeInTheDocument()
expect(screen.getByText('Sign Up')).toBeInTheDocument()
expect(screen.queryByText('Logout')).not.toBeInTheDocument()
})
it('should show user menu and logout when authenticated', () => {
render(<Navbar isAuthenticated={true} user={{ name: 'Test User' }} />)
expect(screen.getByText('Welcome, Test User')).toBeInTheDocument()
expect(screen.getByText('Logout')).toBeInTheDocument()
expect(screen.queryByText('Login')).not.toBeInTheDocument()
})
})
+76
View File
@@ -0,0 +1,76 @@
import { describe, it, expect } from 'vitest'
describe('Auth Utilities', () => {
describe('Email validation', () => {
// Assuming validateEmail is in the auth utility
const validateEmail = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
it('should return true for valid emails', () => {
expect(validateEmail('test@example.com')).toBe(true)
expect(validateEmail('user.name+tag@example.co.uk')).toBe(true)
})
it('should return false for invalid emails', () => {
expect(validateEmail('invalid-email')).toBe(false)
expect(validateEmail('test@.com')).toBe(false)
expect(validateEmail('@example.com')).toBe(false)
})
})
describe('Password strength', () => {
// Assuming checkPasswordStrength is in the auth utility
const checkPasswordStrength = (pass: string) => {
if (pass.length < 8) return 'weak'
if (!/[A-Z]/.test(pass) || !/[0-9]/.test(pass) || !/[^a-zA-Z0-9]/.test(pass)) return 'medium'
return 'strong'
}
it('should return weak for passwords under 8 characters', () => {
expect(checkPasswordStrength('pass')).toBe('weak')
})
it('should return medium for passwords without symbols or numbers', () => {
expect(checkPasswordStrength('password123')).toBe('medium')
})
it('should return strong for passwords with mixed case, numbers, and symbols', () => {
expect(checkPasswordStrength('StrongPass123!')).toBe('strong')
})
})
describe('Login/Logout operations', () => {
it('should handle login successfully with correct credentials', async () => {
// Mock login function
const login = async (user: string, pass: string) => {
if (user === 'user@example.com' && pass === 'password') {
return { success: true, token: 'fake-jwt-token' }
}
return { success: false }
}
const result = await login('user@example.com', 'password')
expect(result.success).toBe(true)
expect(result).toHaveProperty('token', 'fake-jwt-token')
})
it('should fail login with incorrect credentials', async () => {
const login = async (user: string, pass: string) => {
if (user === 'user@example.com' && pass === 'password') {
return { success: true, token: 'fake-jwt-token' }
}
return { success: false }
}
const result = await login('user@example.com', 'wrongpassword')
expect(result.success).toBe(false)
})
it('should handle logout successfully', async () => {
// Mock logout function
const logout = async () => true
const result = await logout()
expect(result).toBe(true)
})
})
})
+1 -1
View File
File diff suppressed because one or more lines are too long
+15
View File
@@ -0,0 +1,15 @@
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import path from 'path'
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./vitest.setup.ts'],
alias: {
'@': path.resolve(__dirname, './src'),
},
},
})
+1
View File
@@ -0,0 +1 @@
import '@testing-library/jest-dom/vitest'